# cryptoZombie 知识点总结 **Published by:** [0xmind](https://paragraph.com/@0xmind/) **Published on:** 2022-03-13 **URL:** https://paragraph.com/@0xmind/cryptozombie ## Content 声明版本pragma solidity ^0.4.19; contract HelloWorld { } 声明状态变量contract Example { // 这个无符号整数将会永久的被保存在区块链中 uint myUnsignedInteger = 100; } // 其中 uint 实际上指 uint256, 又分 uint8/uint16/uint32 等位数 运算加法: x + y 减法: x - y, 乘法: x * y 除法: x / y 取模 / 求余: x % y (例如, 13 % 5 余 3, 因为13除以5,余3) 乘方 x ** y = x^y 结构体struct Person { uint age; string name; } 数组// 分为静态数组和动态数组 // 固定长度为2的静态数组: uint[2] fixedArray; // 固定长度为5的string类型的静态数组: string[5] stringArray; // 动态数组,长度不固定,可以动态添加元素: uint[] dynamicArray; // 公共数组 Person[] public people; // 其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式 函数function eatHamburgers(string _name, uint _amount) { } 私有 / 公共函数// 我们合约中的其它函数才能够调用这个函数 // 继承自父合约的子合约也不可以调用这个函数 uint[] numbers; function _addToArray(uint _number) private { numbers.push(_number); } 函数更多属性// 返回值 string greeting = "What's up dog"; function sayHello() public returns (string) { return greeting; } // 修饰符 view 只能读取数据不能更改数据 function sayHello() public view returns (string) {} // 修饰符 pure 这个函数甚至都不访问应用里的数据 function _multiply(uint a, uint b) private pure returns (uint) { return a * b; } Keccak256 和 类型转换// 散列函数,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。 类型转换uint8 a = 5; uint b = 6; // 将会抛出错误,因为 a * b 返回 uint, 而不是 uint8: uint8 c = a * b; // 我们需要将 b 转换为 uint8: uint8 c = a * uint8(b); 事件// 这里建立事件 event IntegersAdded(uint x, uint y, uint result); function add(uint _x, uint _y) public { uint result = _x + _y; //触发事件,通知app IntegersAdded(_x, _y, result); return result; } // 前端监听 YourContract.IntegersAdded(function(error, result) { // 干些事 }) 使用 web3.js// 下面是调用合约的方式: var abi = /* abi是由编译器生成的 */ var ZombieFactoryContract = web3.eth.contract(abi) var contractAddress = /* 发布之后在以太坊上生成的合约地址 */ var ZombieFactory = ZombieFactoryContract.at(contractAddress) // `ZombieFactory` 能访问公共的函数以及事件 // 某个监听文本输入的监听器: $("#ourButton").click(function(e) { var name = $("#nameInput").val() //调用合约的 `createRandomZombie` 函数: ZombieFactory.createRandomZombie(name) }) // 监听 `NewZombie` 事件, 并且更新UI var event = ZombieFactory.NewZombie(function(error, result) { if (error) return generateZombie(result.zombieId, result.name, result.dna) }) // 获取 Zombie 的 dna, 更新图像 function generateZombie(id, name, dna) { let dnaStr = String(dna) // 如果dna少于16位,在它前面用0补上 while (dnaStr.length < 16) dnaStr = "0" + dnaStr let zombieDetails = { // 前两位数构成头部.我们可能有7种头部, 所以 % 7 // 得到的数在0-6,再加上1,数的范围变成1-7 // 通过这样计算: headChoice: dnaStr.substring(0, 2) % 7 + 1, // 我们得到的图片名称从head1.png 到 head7.png // 接下来的两位数构成眼睛, 眼睛变化就对11取模: eyeChoice: dnaStr.substring(2, 4) % 11 + 1, // 再接下来的两位数构成衣服,衣服变化就对6取模: shirtChoice: dnaStr.substring(4, 6) % 6 + 1, //最后6位控制颜色. 用css选择器: hue-rotate来更新 // 360度: skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360), eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360), clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360), zombieName: name, zombieDescription: "A Level 1 CryptoZombie", } return zombieDetails } 映射(Mapping)和地址(Address)// 以太坊区块链由 _ account _ (账户)组成 // _映射_ 是另一种在 Solidity 中存储有组织数据的方法 映射可以理解为一个包含一个或多个 key-value 键值对的对象 Msg.sender全局变量,指的是当前调用者(或智能合约)的 address Require// require 使得函数在执行过程中,当不满足某些条件时抛出错误,并停止执行 function sayHiToVitalik(string _name) public returns (string) { // 比较 _name 是否等于 "Vitalik". 如果不成立,抛出异常并终止程序 // (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较 // 两字符串的 keccak256 哈希值来进行判断) require(keccak256(_name) == keccak256("Vitalik")); // 如果返回 true, 运行如下语句 return "Hi!"; } 继承(Inheritance)contract Doge { function catchphrase() public returns (string) { return "So Wow CryptoDoge"; } } contract BabyDoge is Doge { function anotherCatchphrase() public returns (string) { return "Such Moon BabyDoge"; } } 引入(Import)import "./someothercontract.sol"; contract newContract is SomeOtherContract { } Storage与Memory// Storage 变量是指永久存储在区块链中的变量。 // Memory 变量则是临时的,当外部函数对某合约调用完成时,内存型变量即被移除。 internal(内部) 和 external(外部)// internal 和 private 类似,不过, 如果某个合约继承自其父合约,这个合约即可以访问父合约中定义的“内部”函数。 // external 与public 类似,只不过这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。 与其他合约的交互-接口// 先定义一个 interface // 不使用大括号({ 和 })定义函数体,我们单单用分号(;)结束了函数声明 contract NumberInterface { function getNum(address _myAddress) public view returns (uint); } // 在自己的合约中使用 contract MyContract { address NumberInterfaceAddress = 0xab38...; // ^ 这是FavoriteNumber合约在以太坊上的地址 NumberInterface numberContract = NumberInterface(NumberInterfaceAddress); // 现在变量 `numberContract` 指向另一个合约对象 function someFunction() public { // 现在我们可以调用在那个合约中声明的 `getNum`函数: uint num = numberContract.getNum(msg.sender); // ...在这儿使用 `num`变量做些什么 } } 处理多返回值function multipleReturns() internal returns(uint a, uint b, uint c) { return (1, 2, 3); } function processMultipleReturns() external { uint a; uint b; uint c; // 这样来做批量赋值: (a, b, c) = multipleReturns(); } // 或者如果我们只想返回其中一个变量: function getLastReturnValue() external { uint c; // 可以对其他字段留空: (,,c) = multipleReturns(); } Ownable Contracts// OpenZeppelin库的Ownable 合约 contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // function Ownable()是一个 _ constructor_ (构造函数),构造函数不是必须的,它与合约同名,构造函数一生中唯一的一次执行,就是在合约最初被创建的时候。 function Ownable() public { owner = msg.sender; } // 在其他语句执行前,为它检查下先验条件。 在这个例子中,我们就可以写个修饰符 onlyOwner 检查下调用者,确保只有合约的主人才能运行本函数 modifier onlyOwner() { require(msg.sender == owner); _; } /** * @dev Allows the current owner to transfer control of the contract to a newOwner. * @param newOwner The address to transfer ownership to. */ function transferOwnership(address newOwner) public onlyOwner { require(newOwner != address(0)); OwnershipTransferred(owner, newOwner); owner = newOwner; } } 合约继承 Ownableimport "./ownable.sol"; contract ZombieFactory is Ownable {} gas优化1// 把 uint 绑定到 struct 里面 // 如果一个 struct 中有多个 uint,则尽可能使用较小的 uint, Solidity 会将这些 uint 打包在一起,从而占用较少的存储空间 struct NormalStruct { uint a; uint b; uint c; } struct MiniMe { uint32 a; uint32 b; uint c; } // 因为使用了结构打包,`mini` 比 `normal` 占用的空间更少 NormalStruct normal = NormalStruct(10, 20, 30); MiniMe mini = MiniMe(10, 20, 30); // 当 uint 定义在一个 struct 中的时候,尽量使用最小的整数子类型以节约空间。 并且把同样类型的变量放一起(即在 struct 中将把变量按照类型依次放置) 时间单位// Solidity 使用自己的本地时间单位 // 变量 now 将返回当前的unix时间戳(自1970年1月1日以来经过的秒数) // Solidity 还包含秒(seconds),分钟(minutes),小时(hours),天(days),周(weeks) 和 年(years) 等时间单位 // 必须使用 uint32(...) 进行强制类型转换,因为 now 返回类型 uint256。 // 所以我们需要明确将它转换成一个 uint32 类型的变量 gas优化2// 使用 view 查询数据,但是注意: // 如果一个 view 函数在另一个函数的内部被调用, // 而调用函数与 view 函数的不属于同一个合约,也会产生调用成本。 //这是因为如果主调函数在以太坊创建了一个事务,它仍然需要逐个节点去验证。 //所以标记为 view 的函数只有在外部调用时才是免费的。(在所能只读的函数上标记上表示“只读”的“external view 声明) 修饰符 payable一种可以接收以太的特殊函数contract OnlineStore { function buySomething() external payable { // 检查以确定0.001以太发送出去来运行函数: require(msg.value == 0.001 ether); // 如果为真,一些用来向函数调用者发送数字内容的逻辑 transferThing(msg.sender); } } 随机数这个方法首先拿到now的时间戳、msg.sender、 以及一个自增数nonce(一个仅会被使用一次的数,这样我们就不会对相同的输入值调用一次以上哈希函数了)。// 生成一个0到100的随机数: uint randNonce = 0; uint random = uint(keccak256(now, msg.sender, randNonce)) % 100; randNonce++; uint random2 = uint(keccak256(now, msg.sender, randNonce)) % 100; 转移标准ERC721 规范有两种不同的方法来转移代币第一种方法是代币的拥有者调用transfer 方法,传入他想转移到的 address 和他想转移的代币的 _tokenId。第二种方法是代币拥有者首先调用 approve,然后传入与以上相同的参数。接着,该合约会存储谁被允许提取代币,通常存储到一个 mapping (uint256 => address) 里。然后,当有人调用 takeOwnership 时,合约会检查 msg.sender 是否得到拥有者的批准来提取代币,如果是,则将代币转移给他。预防溢出 假设我们有一个uint8, 只能存储8 bit数据。这意味着我们能存储的最大数字就是二进制11111111(或者说十进制的 2^8 - 1 = 255)uint8 number = 255; number++; // number = 0 使用safeMathimport "safemath.sol" contract Hello{ // 声明了 using SafeMath for uint 后,我们用来调用这些方法的 uint 就自动被作为第一个参数传递进去了 using SafeMath for uint; using SafeMath32 for uint32; using SafeMath16 for uint16; uint test = 2; test = test.mul(3); // 等于 test = test * 3 } 注释 - 使用 natspec 标准/// @title 一个简单的基础运算合约 /// @author H4XF13LD MORRIS 💯💯😎💯💯 /// @notice 现在,这个合约只添加一个乘法 contract Math { /// @notice 两个数相乘 /// @param x 第一个 uint /// @param y 第二个 uint /// @return z (x * y) 的结果 /// @dev 现在这个方法不检查溢出 function multiply(uint x, uint y) returns (uint z) { // 这只是个普通的注释,不会被 natspec 解释 z = x * y; } } ## Publication Information - [0xmind](https://paragraph.com/@0xmind/): Publication homepage - [All Posts](https://paragraph.com/@0xmind/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@0xmind): Subscribe to updates - [Twitter](https://twitter.com/no1harm): Follow on Twitter