淘先锋技术网

首页 1 2 3 4 5 6 7

目录

学习目的

状态变量 State variables

无符号整型: uint

数学运算 Math Operation

结构体 Structs

数组 Arrays

公共数组

函数声明 Function Declarations

结构和数组的使用

私有/公有的函数

More on Functions

返回值

函数修饰符

运用

Keccak256 and Typecasting

Keccak256

Typecasting

运用

整合一下

事件 Events

运用

Web3.js


学习目的

做一个关于zombie的dapp,学习solidity及其相关内容。


每次写合约的基本骨架

以ZombieFactory命名

pragma solidity >=0.5.0 <0.6.0;//solidity version

contract ZombieFactory{

}

状态变量 State variables

State variables are permanently stored in contract storage. This means they're written to the Ethereum blockchain. Think of them like writing to a DB.

状态变量永久存储在合约存储中,这意味着会写入以太坊区块链中。

也可以认为它们写入了数据库中。

无符号整型: uint

uint非负。

注意:在Solidity中,uint实际上是uint256(一个256位无符号整数)的别名。您可以用更少的位来声明uint-uint8、uint16、uint32等。但一般情况下只需要使用uint。

声明一个名为dnaDigits的uint,并将其设置为16。

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16; //start here
}


数学运算 Math Operation

+ , - , * , / ,%  基本运算与习惯一致,不过Solidity还支持指数运算符 **

uint x = 5 ** 2; // equal to 5^2 = 25

为了确保僵尸的DNA只有16个字符,让我们制作另一个等于10^16的uint。这样,我们可以稍后使用模运算符%将整数缩短为16位。
创建一个名为dnaModules的uint,并将其设置为dnaDigits的10次方。

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {
    uint dnaDigits = 16;
    //start here
    uint dnaModulus = 10 ** dnaDigits;
}

结构体 Structs

类似C语言的结构体

struct Person {
  uint age;
  string name;
}

注意,我们刚刚引入了一个新类型string。字符串用于任意长度的UTF-8数据。

dapp中,我们将要创建一些僵尸。僵尸将具有多个属性,因此这是结构的完美用例。

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    // start here
    struct Zombie {
        uint dna;
        string name;
    }
}

数组 Arrays

Solidity中有两种类型的数组:固定fixed 数组和动态dynamic 数组

还可以创建结构数组。使用的Zombie 结构:

Zombie[] zombie;

还记得状态变量永久存储在区块链中吗?因此,像这样创建一个动态结构数组对于在合同中存储结构化数据(有点像数据库)非常有用。

公共数组

您可以将数组声明为public,Solidity将自动为其创建getter方法。语法如下:

Zombie[] public Zombie;

 这样别的合约可以读它,但不能写入它。

 我们想在我们的应用程序中存储一支僵尸大军。我们将向其他应用程序展示我们所有的僵尸,所以我们希望它公开。

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    // start here
    Zombie[] public zombies;
}

函数声明 Function Declarations

类似C语言。

有两种方法可以将参数传递给Solidity函数:
   按值。这意味着Solidity编译器将创建参数值的新副本并将其传递给函数。这允许您的函数修改值,而不必担心初始参数的值发生更改。
  通过引用。这意味着使用对原始变量的…引用来调用函数。因此,如果函数更改了它接收的变量的值,则原始变量的值将更改。

注意:为了将函数参数变量名称与全局变量区分开来,通常(但不是必须)以下划线(_)开头。我们将在整个学习过程中使用该约定。

示例 :

我们提供了有关_name变量应存储在内存中的位置的说明。这是所有引用类型(如数组、结构、映射和字符串)所必需的。

function eatHamburgers(string memory _name, uint _amount) public {

}

创建名为createZombie的公共函数。它应该包含两个参数:_name(字符串)和_dna(uint)。不要忘记使用memory关键字按值传递第一个参数 

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;

    // start here
    function createZombie(string memory _name, uint _dna) public {

    }
}

结构和数组的使用

使用array.push()把对象压入对象数组。

struct Person {
  uint age;
  string name;
}

Person[] public people;

// create a New Person:
Person satoshi = Person(172, "Satoshi");

// Add that person to the Array:
people.push(satoshi); //或者 people.push(Person(16, "Vitalik"));

完善函数体,使其创建一个新的僵尸,并将其添加到僵尸数组中。新僵尸的名字和dna应该来自函数参数。

让我们在一行代码中完成它,以保持整洁。

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;

    function createZombie (string memory _name, uint _dna) public {
        // start here
        zombies.push(Zombie(_name, _dna));
    }

}

私有/公有的函数

在Solidity中,默认情况下函数是public的。

修改为私有如下示例:

uint[] numbers;

function _addToArray(uint _number) private {
  numbers.push(_number);
}

如你所见, 在函数名后修改为private即可。惯例是以下划线()开头的私有函数名。

修改createZombie,使其成为私有函数。别忘了命名惯例!

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;

    function _createZombie(string memory _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    }

}

More on Functions

返回值

string greeting = "What's up dog";

function sayHello() public returns (string memory) {
  return greeting;
}

在Solidity中,函数声明包含返回值returns的类型(在本例中为字符串)。

函数修饰符

我们可以将其声明为一个视图view函数,这意味着它只查看数据,而不修改数据:

function sayHello() public view returns (string memory) {
}

Solidity还包含纯pure函数,这意味着无法访问应用程序中的任何数据:

function _multiply(uint a, uint b) private pure returns (uint) {
  return a * b;
}

这个函数甚至不会从app的状态中读取数据——它的返回值只取决于它的函数参数。所以在本例中,我们将函数声明为纯函数。

注意:可能很难记住何时将函数标记为pure/view。幸运的是,Solidity编译器会发出警告,让您知道何时应该使用这些修饰符之一。

运用

创建名为_generateRandomDna的专用函数。它将接受一个名为_str(a string)的参数,并返回一个uint。不要忘记将_str参数的数据位置设置为memory。
此函数将查看合约的一些变量,但不会修改它们,因此将其标记为view。

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;

    function _createZombie(string memory _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    }

    function _generateRandomDna(string memory _str) private view returns (uint) {

    }
}

Keccak256 and Typecasting

Keccak256

我们希望_generateRandomDna函数返回一个(半)随机uint。我们如何才能做到这一点?

以太坊内置了哈希函数keccak256,这是SHA3的一个版本。哈希函数基本上将输入映射为随机的256位十六进制数。输入中的轻微变化将导致哈希值的大幅变化。

它在以太坊中有很多用途,但目前我们只打算将其用于伪随机数生成。

同样重要的是,keccak256需要一个字节类型的参数。这意味着我们必须在调用keccak256之前“打包”任何参数:

//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5
keccak256(abi.encodePacked("aaaab"));
//b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9
keccak256(abi.encodePacked("aaaac"));

上述代码可以发现,输入中只有1个字符的更改,但返回的值完全不同。

注意:在区块链中安全生成随机数是一个非常困难的问题。我们这里的方法是不安全的,但由于安全并不是僵尸DNA的首要任务,所以它对于我们的目的来说已经足够好了。

Typecasting

数据类型之间的转换。示例:

uint8 a = 5;
uint b = 6;
// throws an error because a * b returns a uint, not uint8:
uint8 c = a * b;
// we have to typecast b as a uint8 to make it work:
uint8 c = a * uint8(b);

运用

第一行代码应该采用abi.encodePacked(_str)的keccak256哈希生成十六进制伪随机数,将其类型化为uint,最后将结果存储在名为rand的uint中。
我们希望DNA只有16位长(还记得我们的dnaModulus吗?)。因此,第二行代码应返回上述值modulus (%)dnaModulus。

pragma solidity ^0.4.25;

contract ZombieFactory {

    uint dnaDigits = 16;
    uint dnaModulus = 10 ** dnaDigits;

    struct Zombie {
        string name;
        uint dna;
    }

    Zombie[] public zombies;

    function _createZombie(string memory _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    }

    function _generateRandomDna(string memory _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;
    }

}

整合一下

我们的随机僵尸生成器即将完成!让我们创建一个将一切联系在一起的公共函数。
我们将创建一个公共函数,该函数接受输入僵尸的名称,并使用该名称创建具有随机DNA的僵尸。

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;

    function _createZombie(string memory _name, uint _dna) private {
        zombies.push(Zombie(_name, _dna));
    } 

    function _generateRandomDna(string memory _str) private view returns (uint) {
        uint rand = uint(keccak256(abi.encodePacked(_str)));
        return rand % dnaModulus;//保证16位
    }

    // start here
    function createRandomZombie(string memory _name) public{
        uint randDna = _generateRandomDna(_name);
        _createZombie(_name,randDna);
    }

}

事件 Events

我们的合约快完成了!现在,让我们添加一个事件。

事件是您的合约将区块链上发生的事情通知给您的app前端的一种方式,它可以“监听”某些事件,并在事件发生时动作。

示例:

// 声明事件
event IntegersAdded(uint x, uint y, uint result);

function add(uint _x, uint _y) public returns (uint) {
  uint result = _x + _y;
  // emit触发一个事件让前端知道该函数动作了:
  emit IntegersAdded(_x, _y, result);
  return result;
}

然后,你的app前端可以监听事件。javascript实现类似于:

YourContract.IntegersAdded(function(error, result) {
  // 写带结果的
})

运用

我们希望每次创建新僵尸时,都有一个事件让前端知道,这样应用程序就可以显示它。

Declare an event called NewZombie. It should pass zombieId (a uint), name (a string), and dna (a uint).

Modify the _createZombie function to fire the NewZombie event after adding the new Zombie to our zombies array.

You're going to need the zombie's id. array.push() returns a uint of the new length of the array - and since the first item in an array has index 0, array.push() - 1 will be the index of the zombie we just added. Store the result of zombies.push() - 1 in a uint called id, so you can use this in the NewZombie event in the next line.

pragma solidity >=0.5.0 <0.6.0;

contract ZombieFactory {

    // declare our event here
    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;
        // and fire it here
        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);
    }

}

 注:事件中参数不是私有的,因此不用_前缀


Web3.js

Solidity合约已完成!现在需要编写一个与合约交互的javascript前端

以太坊有一个名为Web3.js的Javascript库

后面我们将深入讨论如何部署合约设置Web3.js。但现在,我们先看看我们部署的合约与Web3.js交互的一些示例。

// 以下是如何访问合约:
var abi = /* 编译器生成的abi */
var ZombieFactoryContract = web3.eth.contract(abi)
var contractAddress = /* 合约部署后在以太坊上的地址 */
var ZombieFactory = ZombieFactoryContract.at(contractAddress)
// `ZombieFactory` 有权访问合约的公共functions和event

// 接收文本输入的事件监听器:
$("#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)
})

// 获取僵尸dna,更新我们的图像
function generateZombie(id, name, dna) {
  let dnaStr = String(dna)
  // 如果DNA少于16个字符,用前补零填充
  while (dnaStr.length < 16)
    dnaStr = "0" + dnaStr

  let zombieDetails = {
    // 头两个数字组成head。我们有7个可能的heads,所以%7
    // 得到一个0-6的数字,然后加上1使其为1-7。然后我们得到7
    // 我们加载名为“head1.png”到“head7.png”的基于下面数字的图像文件
    // 这些数字:
    headChoice: dnaStr.substring(0, 2) % 7 + 1,
    // 第二组两位数字组成眼睛,11种变化:
    eyeChoice: dnaStr.substring(2, 4) % 11 + 1,
    // 6种衬衫:
    shirtChoice: dnaStr.substring(4, 6) % 6 + 1,
    // 最后6位数字控制颜色。使用CSS过滤器更新:hue-rotate(angle)
    // 参数angle最大为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
}

我们的javascript所做的是获取上面zombieDetails中生成的值,并使用一些基于浏览器的javascript(使用的是Vue.js)来改变图像以及应用CSS过滤器。