作者: 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標準的代幣:
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調用代碼:
js調用代碼、以及前端頁面代碼,後續將會發送到github,還請大家自行查看。