follow the Zombies! 准备工作:浏览此网站https://cryptozombies.io/,并注册一个账号,从最基础的课程学起。
当然只学这个还是太单薄了,看看登链社区和去remix上编译实操,做些题之类的更好
first Class:build your own zombie factories 表面是僵尸工厂,实际上是solidity
内容概括如下:
合约的定义:
pragma solidity ^0.4.19;//必须的一行,表明编译器版本,合约内容一般不可修改 contract Helloworld {//合约内部 }
变量类型
pragma solidity ^0.4.19; contract ZombieFactory { uint dna = 100;//uint 等效于uint256,256位无符号整数 uint8 num=0;//也有uint8,16,32等,一般直接用uint即可 }
数学运算
有加减乘除,以及特殊的乘方:uint x = 5 ** 2;
结构体
struct Zombie { uint dna; string name;//字符串,直接赋值 }
数组
uint[2] fixedArray; string[5] strArray; uint[] dynamicArray;//动态添加元素,不定长度
函数
Zombie[] public zombies;//给定zombies的属性 function createZombie(uint _dna,tsring _name) private //指定函数的属性,私有/公有, { zombies.push(Zombies(_dna,_name)); //沿用之前的Zombie结构体定义,并使用array的push方法 }
返回值和修饰符
string greeting = "What's up dog"; function sayHello() public returns (string) {//指定返回值类型和个数,可以是多个值! return greeting; } function sayHello() public view returns (string) { //view 是函数修饰符,表示函数只能读取数据不能修改数据 } function _multiply(uint a, uint b) private pure returns (uint) { return a * b; //pure也是修饰符,表明这个函数甚至都不访问应用里的数据,完全取决于输入参数 } //修饰符也是可以创建的
keccak256和类型转换
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9 keccak256("aaaac");//一个封装好的哈希函数,返回16进制的uint256 uint8 a = 5; uint b = 6; // 将会抛出错误,因为 a * b 返回 uint, 而不是 uint8: uint8 c = a * b; // 我们需要将 b 转换为 uint8: uint8 c = a * uint8(b);
事件
事件是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。
我们的 JavaScript 所做的就是获取由zombieDetails
产生的数据, 并且利用浏览器里的 JavaScript 神奇功能 (我们用 Vue.js),置换出图像以及使用CSS过滤器。在后面的课程中,你可以看到全部的代码。
// 这里建立事件 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
以太坊的js库,用来调用合约
var abi = var ZombieFactoryContract = web3.eth .contract (abi)var contractAddress = var ZombieFactory = ZombieFactoryContract .at (contractAddress)$("#ourButton" ).click (function (e ) { var name = $("#nameInput" ).val () ZombieFactory .createRandomZombie (name) }) var event = ZombieFactory .NewZombie (function (error, result ) { if (error) return generateZombie (result.zombieId , result.name , result.dna ) }) function generateZombie (id, name, dna ) { let dnaStr = String (dna) while (dnaStr.length < 16 ) dnaStr = "0" + dnaStr let zombieDetails = { headChoice : dnaStr.substring (0 , 2 ) % 7 + 1 , eyeChoice : dnaStr.substring (2 , 4 ) % 11 + 1 , shirtChoice : dnaStr.substring (4 , 6 ) % 6 + 1 , 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 }
小结belike
pragma solidity ^0.4.19; 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 _name, uint _dna) private { zombies.push(Zombie(_name, _dna)); NewZombie(zombies.push()-1,_name,_dna); } function _generateRandomDna(string _str) private view returns (uint) { uint rand = uint(keccak256(_str)); return rand % dnaModulus; } function createRandomZombie(string _name) public { uint randDna = _generateRandomDna(_name); _createZombie(_name, randDna); } }
猎杀时刻!
映射和地址
是新的数据类型。以太坊的区块链由_ account _ (账户)组成,你可以把它想象成银行账户。一个帐户的余额是 以太 (在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样。
地址则是账户的唯一标识符,属于特定的用户或智能合约。
mapping则是以键值对存储数据的方法。例如:
mapping (address => uint) public accountBalance; accountBalance[msg.sender] = 686578;//存入数据
在 Solidity 中,有一些全局变量可以被所有函数调用。 其中一个就是 msg.sender
,它指的是当前调用者(或智能合约)的 address
。功能执行往往需要从外部调用者开始。一个合约在没有人调用时,往往什么都不会做。也就是说msg.sender
总是存在的。
使用 msg.sender
很安全,因为它具有以太坊区块链的安全保障 —— 除非窃取与以太坊地址相关联的私钥,否则是没有办法修改其他人的数据的。
require
当我想要每个人只能调用一次某个函数时 ,很有必要使用require
function sayHiToVitalik(string _name) public returns (string) { // 比较 _name 是否等于 "Vitalik". 如果不成立,抛出异常并终止程序 // (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较 // 两字符串的 keccak256 哈希值来进行判断) require(keccak256(_name) == keccak256("Vitalik")); // 如果返回 true, 运行如下语句 return "Hi!"; }
继承
由于 BabyDoge
是从 Doge
那里 inherits (继承)过来的。 这意味着当你编译和部署了 BabyDoge
,它将可以访问 catchphrase()
和 anotherCatchphrase()
和其他我们在 Doge
中定义的其他公共函数。
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";//simple and stupid
storage和memory
solidity唯二可以存储数据的地方,storage是永久存放在区块链的变量,memory则是临时的,完成对合约的调用就会被移除。
多数时候你用不到他们,因为solidity会默认判断并处理。如函数外声明的变量默认为storage,而函数内声明的默认为memory,在调用后消失。编译器也会在不得不使用这关键字时提醒。
contract SandwichFactory { struct Sandwich { string name; string status; } Sandwich[] sandwiches; function eatSandwich(uint _index) public { // Sandwich mySandwich = sandwiches[_index]; // ^ 看上去很直接,不过 Solidity 将会给出警告 // 告诉你应该明确在这里定义 `storage` 或者 `memory`。 // 所以你应该明确定义 `storage`: Sandwich storage mySandwich = sandwiches[_index]; // ...这样 `mySandwich` 是指向 `sandwiches[_index]`的指针 // 在存储里,另外... mySandwich.status = "Eaten!"; // ...这将永久把 `sandwiches[_index]` 变为区块链上的存储 // 如果你只想要一个副本,可以使用`memory`: Sandwich memory anotherSandwich = sandwiches[_index + 1]; // ...这样 `anotherSandwich` 就仅仅是一个内存里的副本了 // 另外 anotherSandwich.status = "Eaten!"; // ...将仅仅修改临时变量,对 `sandwiches[_index + 1]` 没有任何影响 // 不过你可以这样做: sandwiches[_index + 1] = anotherSandwich; // ...如果你想把副本的改动保存回区块链存储 } }
函数可见性
修饰词external
和internal
能够描述函数可见性,前者代表只能在合约之外调用,后者代表只能在合约内调用。语法和private等相同。
合约之间的交互
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 ) { Kitty storage kit = kitties[_id]; // if this variable is 0 then it's not gestating isGestating = (kit.siringWithId != 0); isReady = (kit.cooldownEndBlock <= block.number); cooldownIndex = uint256(kit.cooldownIndex); nextActionAt = uint256(kit.cooldownEndBlock); siringWithId = uint256(kit.siringWithId); birthTime = uint256(kit.birthTime); matronId = uint256(kit.matronId); sireId = uint256(kit.sireId); generation = uint256(kit.generation); genes = kit.genes; } //假设有一个Cryptokitties的合约内有一个函数如上 //在新合约中应用它的方法如下 contract CryptoKities { function getKitty(uint256 _id) external view returns (//只有external或public可以被外部合约调用 bool isGestating, bool isReady, uint256 cooldownIndex, uint256 nextActionAt, uint256 siringWithId, uint256 birthTime, uint256 matronId, uint256 sireId, uint256 generation, uint256 genes ) ; } contract myContract { address Num = 0xadd2736...;//假设是kitty合约的以太坊地址,实际中不会这么引用 CryptoKitties kittyContract = CtyptoKitties(Num); uint memory tempDangerous;//临时值应当在函数内建立,这里只是示范 (.........tempDangerous) = kittyContract.getKitty(0xadc);//不需要的返回值留空,填入需要的返回值 }
比较,if
和js差不多,但字符串不能直接比较,只能通过keccak256()等方法通过哈希值比较。
新的前端例子
var abi = var ZombieFeedingContract = web3.eth .contract (abi)var contractAddress = var ZombieFeeding = ZombieFeedingContract .at (contractAddress)let zombieId = 1 ;let kittyId = 1 ;let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId$.get (apiUrl, function (data ) { let imgUrl = data.image_url }) $(".kittyImage" ).click (function (e ) { ZombieFeeding .feedOnKitty (zombieId, kittyId) }) ZombieFactory .NewZombie (function (error, result ) { if (error) return generateZombie (result.zombieId , result.name , result.dna ) })
高级solidity知识 隔了一小个月更新的,这段时间做了点以太坊的ctf入门题,学到了挺多,有兴趣来这个网站学知识:Ethereum Overview - CTF Wiki (ctf-wiki.org) 至于做题搜openZeppelin ethernaut
比如和这一章有关联的:
智能协议的永固性
你编译的程序会一直,永久的,不可更改的,存在以太坊上。这就是 Solidity 代码的安全性如此重要的一个原因。如果你的智能协议有任何漏洞,即使你发现了也无法补救。
所以第二章僵尸协议和kitty协议的交互中也应当保证当kitty协议被废弃后,应当仍然能够使用,比如将kitty的合约地址以函数的方式指定。
ownable合约
这是个挺常见的合约,初学者都会遇到,用来指定一个合约的主人(当你部署他的时候)
/** * @title Ownable * @dev The Ownable contract has an owner address, and provides basic authorization control * functions, this simplifies the implementation of "user permissions". */ contract Ownable { address public owner; event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); /** * @dev The Ownable constructor sets the original `owner` of the contract to the sender * account. */ function Ownable() public { owner = msg.sender; } /** * @dev Throws if called by any account other than the owner. */ 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; } }
这里面有点奇奇怪怪的语法糖。比如_;
。原本网站有解释如图
gas
前文一直没讲的东西挺多,比如solidity的运行环境是什么?以太坊。那什么是以太坊?怎么运行它?运行的成本由谁来负担?
gas的单位就是ether或者wei,任何与合约的交互行为都有gas消耗,gas的大小则取决于交互需要的计算资源的大小。开发人员(比如在学习使用solidity的我)可以用侧链上的ether等等来免费测试程序。比如一个网站(ChainList )可以方便的查找并加入侧链,访问侧链网站获取测试币等等。
懒得补充截一手图XD
至于为什么要把小一点的数据排列在一起,可以去查一查solidity的内存空间分配之类,或者在ctfwiki里看看,涉及一个叫内存插槽的东西。
时间单位
就是时间戳,用now
表示,类似于一个全局变量。这个会返回一个unix时间戳