继上篇用php70行代码获取所有以太坊区块链应用代码,获取到以太坊dapp的solidity代码,除了用mythril工具扫描出安全问题,还是得深入分析代码逻辑。然而solidity语法有些不明白的地方,故借着loomnetwork的cryptozombies游戏 学习下用solidity开发区块链的全过程,在此总结分享一下
pragma solidity ^0.4.18;
import 'zeppelin-solidity/contracts/math/SafeMath.sol';
import 'zeppelin-solidity/contracts/ownership/Ownable.sol';
contract ZombieFactory is Ownable {
using SafeMath for uint256;
event NewZombie(uint zombieId, string name, uint dna);//声明事件
uint dnaDigits = 16;
uint dnaModulus = 10 ** dnaDigits;
uint cooldownTime = 1 days;
struct Zombie {
string name;
uint dna;
uint32 level;
uint32 readyTime;
uint16 winCount;
uint16 lossCount;
}
Zombie[] public zombies;
mapping (uint => address) public zombieToOwner;
mapping (address => uint) ownerZombieCount;
function _createZombie(string _name, uint _dna) internal {
uint id = zombies.push(Zombie(_name, _dna, 1, uint32(now + cooldownTime), 0, 0)) - 1;
zombieToOwner[id] = msg.sender;
ownerZombieCount[msg.sender] = ownerZombieCount[msg.sender].add(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 {
require(ownerZombieCount[msg.sender] == 0);
uint randDna = _generateRandomDna(_name);
randDna = randDna - randDna % 100;
_createZombie(_name, randDna);
}
}
pragma solidity ^0.4.11;
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
CryptoZombies.methods.levelUp(zombieId)
.send({ from: userAccount, value: web3js.utils.toWei("0.001","ether") })
contract NumberInterface {
function getNum(address _myAddress) public view returns (uint);
} address NumberInterfaceAddress = 0xab38...;
// ^ 这是FavoriteNumber合约在以太坊上的地址
NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);
pragma solidity ^0.4.19;
/**
* @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;
}
}
function withdraw() external onlyOwner {
owner.transfer(this.balance);
}
contract ERC721 {
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
event Approval(address indexed _owner, address indexed _approved, uint256 _tokenId);
function balanceOf(address _owner) public view returns (uint256 _balance);
function ownerOf(uint256 _tokenId) public view returns (address _owner);
function transfer(address _to, uint256 _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;
}
function transfer(address _to, uint256 _tokenId) public;
function approve(address _to, uint256 _tokenId) public;
function takeOwnership(uint256 _tokenId) public;
window.addEventListener('load', function() {
// 检查web3是否已经注入到(Mist/MetaMask)
if (typeof web3 !== 'undefined') {
// 使用 Mist/MetaMask 的提供者
web3js = new Web3(web3.currentProvider);
} else {
// 处理用户没安装的情况, 比如显示一个消息
// 告诉他们要安装 MetaMask 来使用我们的应用
}
// 现在你可以启动你的应用并自由访问 Web3.js:
startApp()
})
myContract.methods.myMethod(123).call()
myContract.methods.myMethod(123).send({ from: userAccount }).on("receipt", function(receipt) {}).on("error", function(error) {})
//public数组zombies
cryptoZombies.methods.zombies(id).call()
var accountInterval = setInterval(function() {
// 检查账户是否切换
if (web3.eth.accounts[0] !== userAccount) {
userAccount = web3.eth.accounts[0];
// 调用一些方法来更新界面
updateInterface();
}
}, 100);
function startApp() {
var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS";
cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);
var accountInterval = setInterval(function() {
// Check if account has changed
if (web3.eth.accounts[0] !== userAccount) {
userAccount = web3.eth.accounts[0];
// Call a function to update the UI with the new account
getZombiesByOwner(userAccount)
.then(displayZombies);
}
}, 100);
}
function zombieToOwner(id) {
return cryptoZombies.methods.zombieToOwner(id).call()
}
为了筛选仅和当前用户相关的事件,我们的 Solidity 合约将必须使用 indexed 关键字,就像我们在 ERC721 实现中的Transfer 事件中那样:
event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
日志中存储的不同的索引事件就叫不同的主题。事件定义,event transfer(address indexed _from, address indexed _to, uint value)有三个主题,第一个主题为默认主题,即事件签名transfer(address,address,uint256),但如果是声明为anonymous的事件,则没有这个主题;另外两个indexed的参数也会分别形成两个主题,可以分别通过_from,_to主题来进行过滤。如果数组,包括字符串,字节数据做为索引参数,实际主题是对应值的Keccak-256哈希值
在这种情况下, 因为_from 和 _to 都是 indexed,这就意味着我们可以在前端事件监听中过滤事件
cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
.on("data", function(event) {
let data = event.returnValues;
// 当前用户更新了一个僵尸!更新界面来显示
}).on('error', console.error);
我们甚至可以用 getPastEvents 查询过去的事件,并用过滤器 fromBlock 和 toBlock 给 Solidity 一个事件日志的时间范围("block" 在这里代表以太坊区块编号):
cryptoZombies.getPastEvents("NewZombie", { fromBlock: 0, toBlock: 'latest' })
.then(function(events) {
// events 是可以用来遍历的 `event` 对象
// 这段代码将返回给我们从开始以来创建的僵尸列表
});
因为你可以用这个方法来查询从最开始起的事件日志,这就有了一个非常有趣的用例: 用事件来作为一种更便宜的存储。
这里的短板是,事件不能从智能合约本身读取
contract ERC20 {
function totalSupply() constant returns (uint supply);
function balanceOf( address who ) constant returns (uint value);
function allowance( address owner, address spender ) constant returns (uint _allowance);//判断还可以转移多少币
function transfer( address to, uint value) returns (bool ok);
function transferFrom( address from, address to, uint value) returns (bool ok);
function approve( address spender, uint value ) returns (bool ok);
event Transfer( address indexed from, address indexed to, uint value);
event Approval( address indexed owner, address indexed spender, uint value);
}