soildity的在线ide remix :https://remix.ethereum.org/
soildity学习网站 CryptoZombies :#1 Solidity Tutorial & Ethereum Blockchain Programming Course | CryptoZombies
基于CryptoZombies的soildity学习笔记(友链):ZombieBlock | uloveRock?
Lesson 1:创建一个合约
合约
Solidity 的代码都包裹在合约 里面. 一份合约就是以太应币应用的基本模块, 所有的变量和函数都属于一份合约, 它是你所有应用的起点.
Solidity 源码都必须使用 “version pragma” — 标明 Solidity 编译器的版本.
1 2 3 4 5 pragma solidity ^0.4.19;//编译器版本标识 // 合约 contract helloworld { }
变量类型
uint
无符号数据类型, 指其值不能是负数 ,对于有符号的整数存在名为 int
的数据类型。 uint
实际上是uint256
代名词, 一个256位的无符号整数。另有 uint8
, uint16
, uint32
等类型。
可使用如uint8(变量)
进行数据类型转换
数学运算
与其他语言相同,并且支持乘方操作:
1 uint x = 5 ** 2; // equal to 5^2 = 25
结构体
sol可以创建结构体
1 2 3 4 5 6 7 8 9 10 11 struct Person { uint age; string name; } // 创建一个结构体数组 Person[] public people;//public 可公共访问 // 创建一个新person Person satoshi = Person(172, "Sam"); // 将新创建的person添加进people数组: people.push(satoshi);
数组
sol有动态数组和静态数组两种数组。
1 2 3 4 5 6 7 8 // 固定长度为2的静态数组: uint[2] fixedArray; // 固定长度为5的string类型的静态数组: string[5] stringArray; // 动态数组,长度不固定,可以动态添加元素: uint[] dynamicArray; // 结构体类型数组 structName[] structArray;
array.push()
在数组的 尾部 加入新元素 ,所以元素在数组中的顺序就是我们添加的顺序
函数
在 Solidity 中函数定义的句法如下:
1 2 3 4 function functionName1(uint _number) public view returns (string){ } function _functionName2(uint _number) private returns (string,int){ }
函数属性: **public/private:**默认为public公共函数 , 可改为private私有函数.私有函数的名字需要用(_)起始;
返回值:函数的定义里可包含返回值的数据类型(如本例中 string
)。 可以有多个返回值 .
**修饰符view/pure:**view只读函数,pure不访问数据的函数
eccak256
Ethereum 内部有一个散列函数keccak256
,它用了SHA3版本。一个散列函数基本上就是把一个字符串转换为一个256位的16进制数字。
1 2 3 4 //6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5 keccak256("aaaab"); //b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9 keccak256("aaaac");
事件
事件 是合约和区块链通讯的一种机制。可以添加事件供前端监听 。
1 2 3 4 5 6 7 8 9 // 这里建立事件 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; }
web3.js
以太坊有一个 JavaScript 库,名为Web3.js 。
js示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 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 }
第一节课内容总结:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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 { uint id = zombies.push(Zombie(_name, _dna)) - 1; NewZombie(id, _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); } }
Lesson 2:使用多个合约
Address(地址)和Mapping(映射)
以太坊区块链由 _ account _ (账户)组成,你可以把它想象成银行账户。一个帐户的余额是 以太 (在以太坊区块链上使用的币种),你可以和其他帐户之间支付和接受以太币,就像你的银行帐户可以电汇资金到其他银行帐户一样.
每个帐户都有一个Address “地址”,你可以把它想象成银行账号,这是账户唯一的标识符 .我们可以指定地址作为用户的id.
mapping(映射)本质上是存储和查找数据所用的键值对:
1 2 3 4 //对于金融应用程序,将用户的余额保存在一个 uint类型的变量中.即address在此映射上指向一个uint mapping (address => uint) public accountBalance; //或者可以用来通过userId 存储/查找的用户名,即uint在此映射上指向一个address mapping (uint => string) userIdToName;
在 Solidity 中,有一些全局变量可以被所有函数调用。 其中一个就是 msg.sender
,它指的是当前调用者(或智能合约)的 address
。
在 Solidity 中,功能执行始终需要从外部调用者开始。一个合约只会在区块链上什么也不做,除非有人调用其中的函数。所以 msg.sender
总是存在的。
通过msg.sender
可便于更新mapping数据:
1 2 accountBalance[msg.sender] = uint; userIdToName[uint] = msg.sender;
使用 msg.sender
很安全,因为它具有以太坊区块链的安全保障 —— 除非窃取与以太坊地址相关联的私钥,否则是没有办法修改其他人的数据的。
require
require
可以使函数在执行过程中,当不满足某些条件时抛出错误,并停止执行:
1 2 3 4 5 6 7 function sayHiToVitalik(string _name) public returns (string) { // 比较 _name 是否等于 "Vitalik". 如果不成立,抛出异常并终止程序 // (敲黑板: Solidity 并不支持原生的字符串比较, 我们只能通过比较两字符串的 keccak256 哈希值来进行判断) require(keccak256(_name) == keccak256("Vitalik")); // 如果返回 true, 运行如下语句 return "Hi!"; }
编写函数时,用require作验证条件是很有必要的.
inheritance(继承)
合约之间是可以继承的.
1 2 3 4 5 6 7 8 9 10 11 contract Doge { function catchphrase() public returns (string) { return "So Wow CryptoDoge"; } } // 继承自Doge,可以调用catchphrase() contract BabyDoge is Doge { function anotherCatchphrase() public returns (string) { return "Such Moon BabyDoge"; } }
Import(引入)
solidity通过import
引入其他文件:
存储变量
在 Solidity 中,有两个地方可以存储变量。 storage
是永久存储在区块链中的变量。 Memory
存储临时变量,当外部函数对某合约调用完成时,内存型变量即被移除。
大多数时候你都用不到这些关键字,默认情况下 Solidity 会自动处理它们。 状态变量(在函数之外声明的变量)默认为“存储”形式,并永久写入区块链;而在函数内部声明的变量是“内存”型的,它们函数调用结束后消失。但在有些情况(处理函数内的结构体和数组)需要手动声明类型.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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; // ...如果你想把副本的改动保存回区块链存储 } }
函数可见性internal
和 external
合约继承时,是不能继承访问父代的私有函数(private)的.
除 public
和 private
属性之外,Solidity 还使用了另外两个描述函数可见性的修饰词:internal
(内部) 和 external
(外部)。两个修饰词的用法与public
和 private
相同.
internal
和 private
类似,不过, 合约可以继承父合约的内部函数(internal);
external
与public
类似,不过,这些函数只能在合约之外调用 - 它们不能被合约内的其他函数调用。
与其他合约交互
如果我们的合约需要和区块链上的其他的合约会话,则需先定义一个 interface (接口)。
如果有这么一个合约:
1 2 3 4 5 6 7 8 9 10 contract LuckyNumber { mapping(address => uint) numbers; function setNum(uint _num) public { numbers[msg.sender] = _num; } function getNum(address _myAddress) public view returns (uint) { return numbers[_myAddress]; } }
如果我们想要和此合约对话,调用其中的getNum函数,我们需要定义并使用一个接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //合约外定义一个接口 //这个过程虽然看起来像在定义一个合约,但实际上是声明一个没有定义函数体的函数. //由于只声明了函数,此时接口是不能指向合约的 contract NumberInterface { function getNum(address _myAddress) public view returns (uint); } //使用接口: contract MyContract { address NumberInterfaceAddress = 0xab38...; // ^ 这是LuckyNumber合约在以太坊上的地址 NumberInterface numberContract = NumberInterface(NumberInterfaceAddress); // 现在变量 `numberContract` 指向LuckyNumber的地址 function someFunction() public { // 现在我们可以调用在那个合约中声明的 getNum函数: uint num = numberContract.getNum(msg.sender); // ...在这儿使用 `num`变量做些什么 } }
多个返回值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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(); }
if语句
1 2 3 4 5 6 7 function eatBLT(string sandwich) public { // Solidity 并不支持原生的字符串比较, 我们只能通过比较两字符串的 keccak256 哈希值来进行判断 // 看清楚了,当我们比较字符串的时候,需要比较他们的 keccak256 哈希码 if (keccak256(sandwich) == keccak256("BLT")) { eat(); } }
总结:
前端js实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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 ) })
Lesson 3:高级Solidity理论