solidity入门到升仙(一)

437 阅读6分钟

合约

Solidity 的代码都包裹在合约里面. 一份合约就是以太坊应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点.

如下是一份名为 HelloWorld 的空合约:

contract HelloWorld { }

版本指令

所有的 Solidity 源码都必须冠以 "version pragma" — 标明 Solidity 编译器的版本. 以避免将来新的编译器可能破坏你的代码。

例如: pragma solidity ^0.4.19; (当前 Solidity 的最新版本是 0.4.19).

因此结合以上我们可以生成一个基本的合约

pragma solidity ^0.4.19;
​
contract HelloWorld{}

状态变量和整数

所谓状态变量是被永久地保存在合约里,也就是说他们被写入以太坊区块链中。

contract MyGame {
// 这个无符号整数将会永久保存在区块链中
  uint myUnsignedInteger = 100;
}

uint 无符号数据类型, 指其值不能是负数,对于有符号的整数存在名为 int 的数据类型。

** 注: Solidity中,* uint 实际上是 uint256代名词, 一个256位的无符号整数。你也可以定义位数少的uints -- uint8uint16uint32, 等…… 但一般来讲你愿意使用简单的 uint **

数学运算

与javascript中数学运算一致

结构体

有时我们需要一个复杂的数据类型,solidity 提供了结构体,有点类似于其他编程语言的类

struct Person {
  uint age;
  string name;
}

结构体允许你生成一个更复杂的数据类型,它有多个属性。

数组

当你想向其他语言一样建立一个集合,可以使用数组 这样的数据类型,类似js. solidity支持两种数组

  • 静态数组

    • uint[2] fixedArray;
    • string[5] stringArray;
  • 动态数组

    • uint[] dynamicArray;

结合结构体 类型我们可以建立结构体类型的数组

Person[] people;// 此时people就是一个动态的数组,往里添加的数据必须符合Person结构

公共数组

可以使用 public 定义一个公共数组,solidity 会自动创建getter 方法

Person[] public people;
//其它的合约可以从这个数组读取数据(但不能写入数据),所以这在合约中是一个有用的保存公共数据的模式。

定义函数

solidity中定义函数方式和其他编程语言相似

function createGamePlayer(string _name, uint _age){}
​
// 类似TypeScript 函数参数可以在参数前定义类型,函数里的变量习惯用 下划线_ 开头,表示是一个私有变量
​
createGamePlayer("Tom", 18);

使用结构体和数组

根据以上我们可以生成一个Person 结构体的实例,类似其他语言的类实例

struct Person {
  uint age;
  string name;
}
​
Person[] public people;
​
// 创建一个新的Person:
Person john = Person(18, "john");
​
// 将新创建的john添加进people数组:
people.push(john);
​
// 或者合并成一步-----------
people.push(Person(18, "john"));

** 注:array.push() 在数组的 尾部 加入新元素 ,所以元素在数组中的顺序就是我们添加的顺序, 如:

私有 / 公共函数

Solidity 定义的函数的属性默认为公共。 这就意味着任何一方 (或其它合约) 都可以调用你合约里的函数。但是我们经常为了安全性考虑,会将函数定义为私有,只有当需要外界进行调用的时候才将他设置为公共

uint[] numbers;
​
function _addNumber(uint _number) private {
  numbers.push(_number);
}

在函数名字后面使用关键字 private 即可。和函数的参数类似,私有函数的名字用(_)起始。

函数的更多属性

定义函数的时候我们可以规定它的返回值类型 以及 函数的一些修饰符

string sayContent = 'hello world';
​
function sayHello() public view returns (string) {
    return sayContent;
}
​
// 以上函数 sayHello 没有改变solidity中任何值或者写入任何东西,这个函数我们可以定义为 view ,意味它只能读取数据不能更改数据:// Solidity 还支持 pure 函数, 表明这个函数甚至都不访问应用里的数据.类似纯函数
​
function _multiply(uint a, uint b) private pure returns (uint) {
  return a * b;
}

Keccak256 和 类型转换

以太坊内部有一个散列函数keccak256,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。字符串的一个微小变化会引起散列数据极大变化。

// keccak256在以太坊中有很多应用,此处以生成伪随机数
// 6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256("aaaab");
// b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256("aaaac");
// 输入字符串只改变了一个字母,输出就已经天壤之别了

有时候应用中我们需要变换数据类型。如

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

你的 app 前端可以监听这个事件。JavaScript 实现如下:

YourContract.IntegersAdded(function(error, result) {
  // 干些事
})

Web3.js

当solidity合约完成后,我们需要前端来调用这个合约

此时我们可以使用 以太坊 的一个 javascript 库 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
}

[本文参考项目:CryptoZombies​]