follow the Zombies!

准备工作:浏览此网站https://cryptozombies.io/,并注册一个账号,从最基础的课程学起。

当然只学这个还是太单薄了,看看登链社区和去remix上编译实操,做些题之类的更好

first Class:build your own zombie factories

表面是僵尸工厂,实际上是solidity

内容概括如下:

  1. 合约的定义:
pragma solidity ^0.4.19;//必须的一行,表明编译器版本,合约内容一般不可修改
contract Helloworld {//合约内部

}
  1. 变量类型
pragma solidity ^0.4.19;
contract ZombieFactory {
uint dna = 100;//uint 等效于uint256,256位无符号整数
uint8 num=0;//也有uint8,16,32等,一般直接用uint即可
}
  1. 数学运算

​ 有加减乘除,以及特殊的乘方:uint x = 5 ** 2;

  1. 结构体
struct Zombie {
uint dna;
string name;//字符串,直接赋值
}
  1. 数组
uint[2] fixedArray;
string[5] strArray;
uint[] dynamicArray;//动态添加元素,不定长度
  1. 函数
Zombie[] public zombies;//给定zombies的属性
function createZombie(uint _dna,tsring _name) private
//指定函数的属性,私有/公有,
{
zombies.push(Zombies(_dna,_name));
//沿用之前的Zombie结构体定义,并使用array的push方法

}
  1. 返回值和修饰符
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也是修饰符,表明这个函数甚至都不访问应用里的数据,完全取决于输入参数
}
//修饰符也是可以创建的
  1. 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);
  1. 事件

事件是合约和区块链通讯的一种机制。你的前端应用“监听”某些事件,并做出反应。

我们的 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) {
// 干些事
})
  1. Web3.js

以太坊的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
}
  1. 小结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);
}

}

猎杀时刻!

  1. 映射和地址

是新的数据类型。以太坊的区块链由_ account _ (账户)组成,你可以把它想象成银行账户。一个帐户的余额是 以太 (在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样。

地址则是账户的唯一标识符,属于特定的用户或智能合约。

mapping则是以键值对存储数据的方法。例如:

mapping (address => uint) public accountBalance;
accountBalance[msg.sender] = 686578;//存入数据

在 Solidity 中,有一些全局变量可以被所有函数调用。 其中一个就是 msg.sender,它指的是当前调用者(或智能合约)的 address。功能执行往往需要从外部调用者开始。一个合约在没有人调用时,往往什么都不会做。也就是说msg.sender总是存在的。

使用 msg.sender 很安全,因为它具有以太坊区块链的安全保障 —— 除非窃取与以太坊地址相关联的私钥,否则是没有办法修改其他人的数据的。

  1. require

当我想要每个人只能调用一次某个函数时 ,很有必要使用require

function sayHiToVitalik(string _name) public returns (string) {
// 比较 _name 是否等于 "Vitalik". 如果不成立,抛出异常并终止程序
// (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较
// 两字符串的 keccak256 哈希值来进行判断)
require(keccak256(_name) == keccak256("Vitalik"));
// 如果返回 true, 运行如下语句
return "Hi!";
}

  1. 继承

由于 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";
}
}
  1. import
import "./someothercontract.sol";//simple and stupid
  1. 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;
// ...如果你想把副本的改动保存回区块链存储
}
}
  1. 函数可见性

修饰词externalinternal能够描述函数可见性,前者代表只能在合约之外调用,后者代表只能在合约内调用。语法和private等相同。

  1. 合约之间的交互
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);//不需要的返回值留空,填入需要的返回值
}
  1. 比较,if

和js差不多,但字符串不能直接比较,只能通过keccak256()等方法通过哈希值比较。

  1. 新的前端例子
var abi = /* abi generated by the compiler */
var ZombieFeedingContract = web3.eth.contract(abi)
var contractAddress = /* our contract address on Ethereum after deploying */
var ZombieFeeding = ZombieFeedingContract.at(contractAddress)

// 假设我们有我们的僵尸ID和要攻击的猫咪ID
let zombieId = 1;
let kittyId = 1;

// 要拿到猫咪的DNA,我们需要调用它的API。这些数据保存在它们的服务器上而不是区块链上。
// 如果一切都在区块链上,我们就不用担心它们的服务器挂了,或者它们修改了API,
// 或者因为不喜欢我们的僵尸游戏而封杀了我们
let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId
$.get(apiUrl, function(data) {
let imgUrl = data.image_url
// 一些显示图片的代码
})

// 当用户点击一只猫咪的时候:
$(".kittyImage").click(function(e) {
// 调用我们合约的 `feedOnKitty` 函数
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

比如和这一章有关联的:

  1. 智能协议的永固性

​ 你编译的程序会一直,永久的,不可更改的,存在以太坊上。这就是 Solidity 代码的安全性如此重要的一个原因。如果你的智能协议有任何漏洞,即使你发现了也无法补救。

​ 所以第二章僵尸协议和kitty协议的交互中也应当保证当kitty协议被废弃后,应当仍然能够使用,比如将kitty的合约地址以函数的方式指定。

  1. 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;
}

}

这里面有点奇奇怪怪的语法糖。比如_;。原本网站有解释如图

image-20240507211339431

  1. gas

​ 前文一直没讲的东西挺多,比如solidity的运行环境是什么?以太坊。那什么是以太坊?怎么运行它?运行的成本由谁来负担?

image-20240507211922188

​ gas的单位就是ether或者wei,任何与合约的交互行为都有gas消耗,gas的大小则取决于交互需要的计算资源的大小。开发人员(比如在学习使用solidity的我)可以用侧链上的ether等等来免费测试程序。比如一个网站(ChainList)可以方便的查找并加入侧链,访问侧链网站获取测试币等等。

image-20240507213134049

懒得补充截一手图XD

至于为什么要把小一点的数据排列在一起,可以去查一查solidity的内存空间分配之类,或者在ctfwiki里看看,涉及一个叫内存插槽的东西。

  1. 时间单位

​ 就是时间戳,用now表示,类似于一个全局变量。这个会返回一个unix时间戳

image-20240507214143068