作者: AlexTan CSDN: http://blog.csdn.net/alextan_ Github: https://github.com/AlexTan-b-z e-mail: [email protected]
不好意思,各位,现在才发代码。
上一篇博文"以太坊+IPFS+WEB 电商平台开发讲解"介绍了用以太访+IPFS实现电商平台的思路、合约接口的实现以及一些相关的基本概念。这篇博文将讲解具体的一个简单的实战项目,及用以太访+IPFS实现的一个云存储,并用ERC20标准实现了自己的代币,以太访加IPFS实现存储就送代币。
-
本系统用IPFS来充当存储介质,及用户所上传的东西都是存在IPFS上的,IPFS会返回一个地址(什么是地址?简单理解,你可以通过地址来找到你所存储文件的内容)。
-
用合约来存储用户以及每个用户所存储的IPFS地址
-
用合约实现一个ERC20标准的代币。代币名字叫CloudB,即云币,总发行量10000000。
-
起初每存一次文件便会赠送10个云币,但会花费少量的gas费。
-
待10000000个云币赠送完后,每存储一个文件会花费2个云币以及少量的gas费。
-
查看文件不会产生任何费用。
ERC20是以太坊定义的一个代币标准。要求我们在实现代币的时候必须要遵守的协议,如指定代币名称、总量、实现代币交易函数等,只有支持了协议才能被以太坊钱包支持。其接口如下:
contract ERC20Interface {
string public constant name = "Token Name";
string public constant symbol = "SYM";
uint8 public constant decimals = 18; // 18 is the most common number of decimal places
function totalSupply() public constant returns (uint);
function balanceOf(address tokenOwner) public constant returns (uint balance);
function allowance(address tokenOwner, address spender) public constant returns (uint remaining);
function transfer(address to, uint tokens) public returns (bool success);
function approve(address spender, uint tokens) public returns (bool success);
function transferFrom(address from, address to, uint tokens) public returns (bool success);
event Transfer(address indexed from, address indexed to, uint tokens);
event Approval(address indexed tokenOwner, address indexed spender, uint tokens);
}
简单说明一下:
-
name : 代币名称;
-
symbol: 代币符号;
-
decimals: 代币小数点位数,代币的最小单位, 18表示我们可以拥有 .0000000000000000001单位个代币;
-
totalSupply() : 发行代币总量;
-
balanceOf(): 查看对应账号的代币余额;
-
transfer(): 实现代币交易,用于给用户发送代币(从我们的账户里);
-
transferFrom(): 实现代币用户之间的交易;
-
allowance(): 控制代币的交易,如可交易账号及资产;
-
approve(): 允许用户可花费的代币数;
代币合约代码(TokenERC20.sol):
pragma solidity ^0.4.16;
interface tokenRecipient { function receiveApproval(address _from, uint256 _value, address _token, bytes _extraData) public; }
contract TokenERC20 {
string public name;
string public symbol;
uint8 public decimals = 18; // decimals 可以有的小数点个数,最小的代币单位。18 是建议的默认值
uint256 public totalSupply;
// 用mapping保存每个地址对应的余额
mapping (address => uint256) public balanceOf;
// 存储对账号的控制
mapping (address => mapping (address => uint256)) public allowance;
// 事件,用来通知客户端交易发生
event Transfer(address indexed from, address indexed to, uint256 value);
// 事件,用来通知客户端代币被消费
event Burn(address indexed from, uint256 value);
/**
* 初始化构造
*/
function TokenERC20(uint256 initialSupply, string tokenName, string tokenSymbol, address rootWallet) public {
totalSupply = initialSupply * 10 ** uint256(decimals); // 供应的份额,份额跟最小的代币单位有关,份额 = 币数 * 10 ** decimals。
balanceOf[rootWallet] = totalSupply; // 指定账户地址拥有所有的代币
name = tokenName; // 代币名称
symbol = tokenSymbol; // 代币符号
}
/**
* 代币交易转移的内部实现
*/
function _transfer(address _from, address _to, uint _value) internal {
// 确保目标地址不为0x0,因为0x0地址代表销毁
require(_to != 0x0);
// 检查发送者余额
require(balanceOf[_from] >= _value);
// 确保转移为正数个
require(balanceOf[_to] + _value > balanceOf[_to]);
// 以下用来检查交易,
uint previousBalances = balanceOf[_from] + balanceOf[_to];
// Subtract from the sender
balanceOf[_from] -= _value;
// Add the same to the recipient
balanceOf[_to] += _value;
Transfer(_from, _to, _value);
// 用assert来检查代码逻辑。
assert(balanceOf[_from] + balanceOf[_to] == previousBalances);
}
/**
* 代币交易转移
* 从自己(创建交易者)账号发送`_value`个代币到 `_to`账号
*
* @param _to 接收者地址
* @param _value 转移数额
*/
function transfer(address _to, uint256 _value) public {
_transfer(msg.sender, _to, _value);
}
/**
* 账号之间代币交易转移
* @param _from 发送者地址
* @param _to 接收者地址
* @param _value 转移数额
*/
function transferFrom(address _from, address _to, uint256 _value) public payable returns (bool success) {
require(_value <= allowance[_from][msg.sender]); // Check allowance
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}
/**
* 设置某个地址(合约)可以创建交易者名义花费的代币数。
*
* 允许发送者`_spender` 花费不多于 `_value` 个代币
*
* @param _spender The address authorized to spend
* @param _value the max amount they can spend
*/
function approve(address _spender, uint256 _value) public
returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}
/**
* 设置允许一个地址(合约)以我(创建交易者)的名义可最多花费的代币数。
*
* @param _spender 被授权的地址(合约)
* @param _value 最大可花费代币数
* @param _extraData 发送给合约的附加数据
*/
function approveAndCall(address _spender, uint256 _value, bytes _extraData)
public
returns (bool success) {
tokenRecipient spender = tokenRecipient(_spender);
if (approve(_spender, _value)) {
// 通知合约
spender.receiveApproval(msg.sender, _value, this, _extraData);
return true;
}
}
/**
* 销毁我(创建交易者)账户中指定个代币
*/
function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value); // Check if the sender has enough
balanceOf[msg.sender] -= _value; // Subtract from the sender
totalSupply -= _value; // Updates totalSupply
Burn(msg.sender, _value);
return true;
}
/**
* 销毁用户账户中指定个代币
*
* Remove `_value` tokens from the system irreversibly on behalf of `_from`.
*
* @param _from the address of the sender
* @param _value the amount of money to burn
*/
function burnFrom(address _from, uint256 _value) public returns (bool success) {
require(balanceOf[_from] >= _value); // Check if the targeted balance is enough
require(_value <= allowance[_from][msg.sender]); // Check allowance
balanceOf[_from] -= _value; // Subtract from the targeted balance
allowance[_from][msg.sender] -= _value; // Subtract from the sender's allowance
totalSupply -= _value; // Update totalSupply
Burn(_from, _value);
return true;
}
}
存储合约里实现了,upload上传(实现了赠送代币以及花费代币)、download下载、获取用户所存储文件的个数等函数。具体代码如下:
SaveCloud.sol:
pragma solidity ^0.4.16;
import "./TokenERC20.sol";
contract SaveCloud {
enum IsFinish {Yes, No}
IsFinish isFinish; // 用于判断代币是否已经赠送完
address private owner; // 合约的创建者
uint public saveIndex; // 合约存储文件的总数量
mapping (address=>string[]) public ipfs; // 用于存放每个用户所存储到ipfs的ipfs地址 address => 用户地址,string[] => ipfs地址
TokenERC20 public token; // 代币合约的实例化对象
event Upload(string ipfsAddress );
event Download(uint number);
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
function SaveCloud() public {
/* 构造函数,部署合约时便会调用这个函数 */
owner = msg.sender;
saveIndex = 0;
isFinish = IsFinish.No;
}
function initToken(address tokenAddr) public onlyOwner {
/* 实例化代币合约对象,只有创建合约者才能调用此函数 */
token = TokenERC20(tokenAddr);
}
function upload(string ipfsAddress) public payable returns (bool success) {
if (token.balanceOf(address(this)) >= 10 && isFinish == IsFinish.No) {
token.transfer(msg.sender, 10); // 合约给sender转代币,存东西送代币
}else {
isFinish = IsFinish.Yes;
}
if (isFinish == IsFinish.Yes) {
token.transferFrom(msg.sender, owner, 2);
// 需要aprove(授权),如果不加aprove的话,谁都可以来调这个函数花别人的代币了,存东西,花代币,代币转给合约账户
}
ipfs[msg.sender].push(ipfsAddress);
saveIndex += 1;
Upload(ipfsAddress);
return true;
}
function download(uint number) external returns (string ipfsAddr) {
/* 获取用户的所存放文件的位置(ipfs地址) */
Download(number);
return ipfs[msg.sender][number];
}
function getLength() external view returns (uint length) {
/* 获取用户所存放文件的个数 */
return ipfs[msg.sender].length;
}
}
笔者使用的是truffle框架,其本地部署代码如下:
2_deploy_contracts.js:
var TokenERC20 = artifacts.require("./TokenERC20.sol");
var SaveCloud = artifacts.require("./SaveCloud.sol");
module.exports = function(deployer) {
deployer.deploy(SaveCloud).then(function() {
/* 实例化SaveCloud合约后实例化代币合约 */
return deployer.deploy(TokenERC20, 10000000, "cloudb", "CloudB", SaveCloud.address);
})
};
js调用代码、以及前端页面代码,代码已发github,还请大家自行查看。