用以太坊开发框架Truffle开发智能合约实践攻略(代码详解)

image

1

TRUFFLE是什么?

Truffle是一个世界级的开发环境,测试框架,以太坊的资源管理通道,致力于让以太坊上的开发变得简单,Truffle有如下:javascript

  • 内置的智能合约编译,连接,部署和二进制文件的管理。html

  • 快速开发下的自动合约测试。java

  • 脚本化的,可扩展的部署与发布框架。node

  • 部署到无论多少的公网或私网的网络环境管理功能react

  • 使用EthPM&NPM提供的包管理,使用ERC190标准。webpack

  • 与合约直接通讯的直接交互控制台(写完合约就能够命令行里验证了)。git

  • 可配的构建流程,支持紧密集成。github

  • 在Truffle环境里支持执行外部的脚本。web

【说明】更多以太坊术语可参考此篇文章: https://www.jianshu.com/p/03666198619dnpm

1.1 TRUFFLE的安装

在Ubuntu命令上窗口输入如下命令,完成安装:

$ npm install -g truffle

若是安装成功,可输入truffle version名称,正常状况下会有版本显示:

image

truffle version

环境要求

NodeJS 5.0+ Windows,Linux(推荐Ubuntu),或Mac OS X

Truffle客户端

有许多的以太坊客户端能够选择。咱们推荐在开发和部署时使用不一样客户端。

适用开发的客户端

  • EtherumJS TestRPC

当开发基于Truffle的应用时,咱们推荐使用EthereumJS TestRPC。它是一个完整的在内存中的区块链仅仅存在于你开发的设备上。它在执行交易时是实时返回,而不等待默认的出块时间,这样你能够快速验证你新写的代码,当出现错误时,也能即时反馈给你。它同时仍是一个支持自动化测试的功能强大的客户端。Truffle充分利用它的特性,能将测试运行时间提速近90%。

适用正式发布的客户端

  • Geth (go-ethereum)

  • WebThree(cpp-ethereum)

  • More

对此有许多官方和非官方的以太坊客户端可供选择。最好使用TestRPC客户端充分测试后,再使用这些客户端。这些是完整的客户端实现,包括挖矿,网络,块及交易的处理,Truffle能够在不须要额外配置的状况下发布到这些客户端。

当发布到私有网络中

私人网络中使用了相同的技术,但却有不一样的配置。因此你能够将上面说起的客户端来运行一个私有的网络,部署到这样的网络也是使用一样的方式。

【说明】做者使用TestRPC和   Geth (go-ethereum)这2种客户端,他们的安装方式参考文章:

https://www.jianshu.com/p/683ea7d62a39

2

下载TRUFFLE MetaCoin样例进行环境搭建实战

2.1 MetaCoin初始化

咱们假设前面的安装和环境搭建已所有成功,此时应该能够直接使用命令truffle了,下面咱们创建一个工做间truffle-workspace,而后在工做间执行:

mkdir MetaCoin

cd MetaCoin truffle unbox metacoin

原来使用truffle init,但如今它存在于unbox。

执行截图以下:

image

下载样例

unbox

Truffle 的盒子Boxs装有不少很是实用的项目样板,可让你忽略一些环境配置问题,从而能够集中与开发你本身的DApp的业务惟一性。除此以外,Truffle Boxes可以容纳其余有用的组件、Solidity合约或者库,先后端视图等等。全部这些都是一个完整的实例Dapp程序。均可如下载下来逐一研究,寻找适合本身公司目前业务模型的组件。

Truffle的官方Boxes地址http://truffleframework.com/boxes/)

能够看到,如今官方盒子还很少,总共7个,有三个是关于react的,两个是truffle本身的项目,能够下载体验,剩下两个是咱们比较关心的,一个是metacoin,很是好的入门示例,另外一个是webpack,顾名思义,它是一套比起metacoin更加完整的模板的存在。既然咱们是初学,下面咱们就从metacoin入手学习。

2.2 目录结构及文件解读

进入metacoin目录,当前目录已经被初始化成一个新的空的以太坊工程,目录结构以下:

  • contracts ConvertLib.sol MetaCoin.sol Migrations.sol placeholder

  • migrations 1_initial_migration.js 2_deploy_contracts.js

  • test metacoin.js TestMetacoin.sol placeholder

  • truffle-config.js

  • truffle.js

初始化文件解释1:Migrations.sol

pragma solidity ^0.4.2;

contract Migrations {  

    address public owner;  

    uint public last_completed_migration;  

    modifier restricted() {    

          if (msg.sender == owner) _;  

    }  

    function Migrations() public {    

       owner = msg.sender;  

    }  

    function setCompleted(uint completed) public restricted {

        last_completed_migration = completed;  

    }  

    function upgrade(address new_address) public restricted {    

        Migrations upgraded = Migrations(new_address); 

        upgraded.setCompleted(last_completed_migration);  

    }

}

上面咱们学习了Solidity具体的类型语法,咱们来分析一下这个文件:

  • 它定义了一个名字为“迁移”的合约

  • 有一个任意访问的全局变量,存储于storage的地址类型变量owner

  • 有一个可任意访问的全局变量,存储于storage的无符号整型类型的变量last_completed_migration

  • modifier下面细说,此处略过

  • msg.sender下面细说,此处略过

  • 构造函数,初始化将发送方赋值给owner保存

  • 一个setCompleted赋值方法,赋值给last_completed_migration,其中该方法被声明为restricted,下面细说,此处略过

  • upgrade方法,调用当前合约本身的方法,获得合约的实例upgraded,而后经过该是咧调用setCompleted赋值方法。

Solidity语法补充说明1:function modifier

modifier的使用方法,就看上面的Migrations合约的例子便可,它能够自动改变函数的行为,例如你能够给他预设一个条件,他会不断检查,一旦符合条件便可走预设分支。它能够影响当前合约以及派生合约。

pragma solidity ^0.4.11;

contract owned {    

       function owned() public { owner = msg.sender; }    

       address owner;   

       // 这里仅定义了一个modifier可是没有使用,它将被子类使用,方法体在这里“_;”,这意味着若是owner调用了这个函数,函数会被执行,其余人调用会抛出一个异常。    

       modifier onlyOwner {        

              require(msg.sender == owner);       

              _;   

       }

}

// 经过is关键字来继承一个合约类,mortal是owned的子类,也叫派生类。

contract mortal is owned {    

       // 当前合约派生了owned,此方法使用了父类的onlyOwner的modifier

      // public onlyOwner, 这种写法挺让人困惑,下面给出了个人思考,暂理解为派生类要使用基类的modifier。    

      function close() public onlyOwner {        

            selfdestruct(owner);    

      }

}

contract priced {    

       // Modifiers能够接收参数    

       modifier costs(uint price) {        

              // 这里modifier方法体是经过条件判断,是否知足,知足则执行“_;”分支。       

             if (msg.value >= price) {            

                 _;        

             }    

       }

}

contract Register is priced, owned {    

       mapping (address => bool) registeredAddresses;    

       uint price;   

       // 构造函数给全局变量price赋值。   

       function Register(uint initialPrice) public { price = initialPrice; }

      // payable关键字重申,若是不声明的话,函数关于以太币交易的操做都会被拒回。    

      function register() public payable costs(price) {        

             registeredAddresses[msg.sender] = true;    }    

      // 此派生类也要使用基类的modifier。    

      function changePrice(uint _price) public onlyOwner {        

             price = _price;    

      }

}

contract Mutex {   

      bool locked;    

      modifier noReentrancy() {        

              require(!locked);        

              locked = true;        

              _;        

              locked = false;    

       }    

       function f() public noReentrancy returns (uint) { 

              require(msg.sender.call());        

              return 7;    

       }

}

又延伸出来一个盲点:require关键字,它是错误判断,提到assert就懂了,官方文档的解释为:

require(bool condition): throws if the condition is not met - to be used for errors in inputs or external components.

总结一下modifier:

  • 声明modifier时,特殊符号“_;”的意思有点像TODO,是一个“占位符”,指出了你要写的具体方法体内容的位置。

  • function close() public onlyOwner,派生类某方法想“如虎添翼”加入基类的某个modifier功能,就能够这样写,这行的具体意思就是:close方法也必须是owner本人执行,不然报错!

Solidity语法补充说明2:Restricting Access

限制访问一种针对合约的常见模式。但其实你永远不可能限制得了任何人或电脑读取你的交易内容或者你的合同状态。你可使用加密增大困难,但你的合约就是用来读取数据的,那么其余人也会看到。因此,其实上面的modifier onlyOwner是一个特别好的可读性极高的限制访问的手段。

那么restricted关键字如何使用呢?

好吧,我刚刚带着modifier的知识从新看了上面的Migrations合约的内容发现,restricted并非关键字,而是modifier的方法名,在其下的想增长该modifier功能的函数中,都使用了public restricted的方式来声明。

说到这里,我又明白了为何要使用public onlyOwner这种写法,由于public是函数可见性修饰符,onlyOwner是自定义的限制访问的modifier方法,他们都是关于函数使用限制方面的,因此会写在一块儿,能够假想一个括号将它俩括起来,他们占一个位置,就是原来属于public|private|internal|external的那个位置。

Solidity语法补充说明3:Special Variables and Functions

这一点很重要了,咱们研究一下Solidity自身携带的特殊变量以及函数:

  • block.blockhash(uint blockNumber) returns (bytes32): 返回参数区块编号的hash值。(范围仅限于最近256块,还不包含固然块)

  • block.coinbase (address): 当前区块矿工地址

  • block.difficulty (uint): 当前区块难度

  • block.gaslimit (uint): 当前区块的gaslimit

  • block.number (uint): 当前区块编号

  • block.timestamp (uint): 当前区块的timestamp,使用UNIX时间秒

  • msg.data (bytes): 完整的calldata

  • msg.gas (uint): 剩余的gas

  • msg.sender (address): 信息的发送方 (当前调用)

  • msg.sig (bytes4): calldata的前四个字节 (i.e. 函数标识符)

  • msg.value (uint): 消息发送的wei的数量

  • now (uint): 当前区块的timestamp (block.timestamp别名)

  • tx.gasprice (uint): 交易的gas单价

  • tx.origin (address): 交易发送方地址(彻底的链调用)

msg有两个属性,一个是msg.sender,另外一个是msg.value,这两个值能够被任何external函数调用,包含库里面的函数。

注意谨慎使用block.timestamp, now and block.blockhash,由于他们都是有可能被篡改的。

初始化文件解释2:MetaCoin.sol

pragma solidity ^0.4.18;

import "./ConvertLib.sol";

// 这是一个简单的仿币合约的例子。它并非标准的可兼容其余币或token的合约,

// 若是你想建立一个标准兼容的token,请转到 https://github.com/ConsenSys/Tokens(TODO:一下子咱们再过去转)

contract MetaCoin {        

            mapping (address => uint) balances;// 定义了一个映射类型变量balances,key为address类型,值为无符整型,应该是用来存储每一个帐户的余额,能够存多个。        

             event Transfer(address indexed _from, address indexed _to, uint256 _value);// Solidity语法event,TODO:见下方详解。        

             function MetaCoin() public {// 构造函数,tx.origin查查上面,找到它会返回交易发送方的地址,也就是说合约实例建立时会默认为当前交易发送方的余额塞10000,单位应该是你的仿币。                

                          balances[tx.origin] = 10000;        

              }        

              function sendCoin(address receiver, uint amount) public returns(bool sufficient) {// 函数声明部分没有盲点,方法名,参数列表,函数可见性,返回值类型定义。               

                          if (balances[msg.sender] < amount) return false;// 若是余额不足,则返回发送币失败                

                          balances[msg.sender] -= amount;// 不然从发送方余额中减去发送值,注意Solidity也有 “-=”,“+=” 的运算符哦 

                          balances[receiver] += amount;// 而后在接收方的余额中加入发送值数量。                

                          Transfer(msg.sender, receiver, amount);// 使用以上event关键字声明的方法                

                          return true;       

               }       

               function getBalanceInEth(address addr) public view returns(uint){// 获取以太币余额                

                          return ConvertLib.convert(getBalance(addr),2);// 调用了其余合约的方法,TODO:稍后介绍ConvertLib合约时说明。        

               }        

               function getBalance(address addr) public view returns(uint) {// 获取当前帐户的仿币余额                

                          return balances[addr];        

                }

}

Solidity语法补充说明4:Events

Events allow the convenient usage of the EVM logging facilities, which in turn can be used to “call” JavaScript callbacks in the user interface of a dapp, which listen for these events.

Events提供了日志支持,进而可用于在用户界面上“调用”dapp JavaScript回调,监听了这些事件。简单来讲,咱们的DApp是基于web服务器上的web3.js与EVM以太坊结点进行交互的,而智能合约是部署在EVM以太坊结点上的。举一个例子:

contract ExampleContract {  

    // some state variables ...  

    function foo(int256 _value) returns (int256) {    

       // manipulate state ...    

       return _value;  

    }

}

合约ExampleContract有个方法foo被部署在EVM的一个结点上运行了,此时用户若是想在DApp上调用合约内部的这个foo方法,如何操做呢,有两种办法:

  • var returnValue = exampleContract.foo.call(2);// 经过web3 的message的call来调用。

  • 合约内部再声明一个event ReturnValue(address indexed _from, int256 _value);并在foo方法内使用该event用来返回方法执行结果。

第一种办法在方法自己比较耗时的状况下会阻塞,或者不会获取到准确的返回值。因此采用第二种办法:就是经过Solidity的关键字event。

event在这里就是一个回调函数的概念,当函数运行结束之后(交易进块),会经过event返回给web3,也就是DApp用户界面相应的结果。这是以太坊一种客户端异步调用方法。关于这个回调,要在DApp使用web3时显示编写:

exampleEvent.watch(function(err, result) {  

  if (err) {    

    console.log(err)    

    return;  

  }  

  console.log(result.args._value)  

  // 检查合约方法是否反返回结果,如有则将结果显示在用户界面而且调用exampleEvent.stopWatching()方法中止异步回调监听。

})

写Solidity最大的不一样在于,咱们要随时计算好咱们的gas消耗,方法的复杂度,变量类型的存储位置(memory,storage等等)都会决定gas的消耗量。

使用event能够得到比storage更便宜的gas消耗。

总结一下event,就是若是你的Dapp客户端web3.js想调用智能合约内部的函数,则使用event做为桥梁,它能方便执行异步调用同时又节约gas消耗。

初始化文件解释3:ConvertLib.sol

pragma solidity ^0.4.4;

library ConvertLib{        

           function convert(uint amount,uint conversionRate) public pure returns (uint convertedAmount)        

           {                

                       return amount * conversionRate;        

           }  

}

与MetaCoin智能合约不一样的是,ConvertLib是由library声明的一个库,它只有一个方法,就是返回给定的两个无符整数值相乘的结果。返回到上面的MetaCoin中该库的使用位置去分析,便可知道,MetaCoin的仿币的价格是以太币的一倍,因此MetaCoin是以以太币为标杆,经过智能合约发布的一个token,仿币。

这彷佛就能够很好地解决我在《以太坊RPC机制与API实例》文章中须要发布三倍以太币的token的需求了,而咱们彻底没必要更改以太坊源码,但那篇文章经过这个需求的路线研究了以太坊的Go源码也算功不可没。

初始化文件解释4:1_initial_migration.js

var Migrations = artifacts.require("./Migrations.sol");

module.exports = function(deployer) {      deployer.deploy(Migrations); } ;

这个js文件是nodejs的写法,看上去它的做用就是部署了上面的Migrations智能合约文件。

初始化文件解释5:2_deploy_contracts.js

var ConvertLib = artifacts.require("./ConvertLib.sol");

var MetaCoin = artifacts.require("./MetaCoin.sol");

module.exports = function(deployer) {  

    deployer.deploy(ConvertLib);  

    deployer.link(ConvertLib, MetaCoin);  

    deployer.deploy(MetaCoin);

};

这个文件是meatcoin智能合约的部署文件,里面约定了部署顺序,依赖关系。这里咱们看到了MetaCoin智能合约是要依赖于库ConvertLib的,因此要先部署ConvertLib,而后link他们,再部署MetaCoin,这部分js的写法能够参照官方文档DEPLOYER API,主要就是介绍了一下deploy、link以及then三个方法的详细用法,不难这里再也不赘述。

初始化文件解释6:truffle-config.js, truffle.js

module.exports = {  

     // See http://truffleframework.com/docs/advanced/configuration

    // to customize your Truffle configuration!};

module.exports = {  

   // See http://truffleframework.com/docs/advanced/configuration  

  // to customize your Truffle configuration!

};

这两个文件也都是nodejs,他们都是配置文件,可能做用域不一样,目前它俩是彻底相同的(由于啥也没有)。咱们去它推荐的网站看一看。给出了一个例子:

module.exports = {  

    networks: {    

        development: {      

            host: "127.0.0.1",      

            port: 8545,      

            network_id: "*" // Match any network id    

            }  

     }

};

这个例子展现了该配置文件能够配置网络环境,暂先到这,之后赶上了针对该配置文件进行研究。

初始化文件解释7:.placeholder

This is a placeholder file to ensure the parent directory in the git repository. Feel free to remove.

翻译过来就是:placeholder文件是用来保证在git库中父级目录的,能够删除。

初始化文件解释8:metacoin.js

和下面的文件同样,他们的功能都是用来作单元测试的,truffle在编译期间会自动执行这些测试脚本。当前文件为js版本,模拟用户在DApp客户端用户界面操做的情形。

var MetaCoin = artifacts.require("./MetaCoin.sol"); // 这与1_initial_migration.js文件的头是同样的,引入了一个智能合约文件。

contract('MetaCoin', function(accounts) {  

      it("should put 10000 MetaCoin in the first account", function() { 

         return MetaCoin.deployed().then(function(instance) {     

           return instance.getBalance.call(accounts[0]);                    

         }).then(function(balance) {      

           assert.equal(balance.valueOf(), 10000, "10000 wasn't in the first account");   

         });  

       }); 

       it("should call a function that depends on a linked library", function() {    

          var meta;   

          var metaCoinBalance;    

          var metaCoinEthBalance;   

          return MetaCoin.deployed().then(function(instance) {      

             meta = instance;      

          return meta.getBalance.call(accounts[0]);               

        }).then(function(outCoinBalance) {      

           metaCoinBalance = outCoinBalance.toNumber();      

           return meta.getBalanceInEth.call(accounts[0]);   

        }).then(function(outCoinBalanceEth) {      

           metaCoinEthBalance = outCoinBalanceEth.toNumber();                     }).then(function() {      

           assert.equal(metaCoinEthBalance, 2 * metaCoinBalance, "Library function returned unexpected function, linkage may be broken");     

          });  

        });  

        it("should send coin correctly", function() {    

           var meta;    

        // Get initial balances of first and second account.    

        var account_one = accounts[0];   

        var account_two = accounts[1];    

        var account_one_starting_balance;   

        var account_two_starting_balance;   

        var account_one_ending_balance;    

        var account_two_ending_balance;    

        var amount = 10;   

        return MetaCoin.deployed().then(function(instance) {      

           meta = instance;      

        return meta.getBalance.call(account_one); 

      }).then(function(balance) {      

        account_one_starting_balance = balance.toNumber();      

        return meta.getBalance.call(account_two);   

      }).then(function(balance) {      

        account_two_starting_balance = balance.toNumber();      

        return meta.sendCoin(account_two, amount, {from: account_one});    

       }).then(function() {      

        return meta.getBalance.call(account_one);   

       }).then(function(balance) {      

        account_one_ending_balance = balance.toNumber();     

        return meta.getBalance.call(account_two);      

       }).then(function(balance) {      

        account_two_ending_balance = balance.toNumber();      

        assert.equal(account_one_ending_balance,

account_one_starting_balance - amount, "Amount wasn't correctly taken from the sender");

        assert.equal(account_two_ending_balance, account_two_starting_balance + amount, "Amount wasn't correctly sent to the receiver");    

        });  

      });

});

咱们来分析一波这个truffle metacoin js版本的单元测试:

  • 直接函数contract走起,第一个参数为智能合约名字,第二个参数为匿名内部函数

  • 匿名函数传入了当前帐户地址,函数体是单元测试集

  • 每一个单元测试是由关键字it函数来作,第一个参数传入单元测试的comments,第二个参数传入一个无参匿名函数

  • 进到无参匿名函数的函数体内,就是正式的单元测试内容,能够定义本身的成员属性,经过调用truffle内部组件自动部署合约逐一测试,使用成员属性接收返回值,最后使用关键字assert来判断是否符合预期。具体业务不详细展开,可根据本身业务内容随意更改。

这是官方文档,详细说明如何使用JS来编写智能合约的单元测试。

http://truffleframework.com/docs/getting_started/javascript-tests

初始化文件解释9:TestMetacoin.sol

好下面来看看Solidity智能合约版本的单元测试。通常来说,这种文件的命名规则是Test加待测智能合约的名字拼串组成。

pragma solidity ^0.4.2;

import "truffle/Assert.sol";

import "truffle/DeployedAddresses.sol";

import "../contracts/MetaCoin.sol";

contract TestMetacoin {  

    function testInitialBalanceUsingDeployedContract() public { 

       MetaCoin meta = MetaCoin(DeployedAddresses.MetaCoin());

       uint expected = 10000;    

       Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");  

    }  

     function testInitialBalanceWithNewMetaCoin() public {

        MetaCoin meta = new MetaCoin();    

        uint expected = 10000;    

        Assert.equal(meta.getBalance(tx.origin), expected, "Owner should have 10000 MetaCoin initially");  

     }

}

继续分析:

  • 首先import了truffle的几个类库,用来支持咱们接下来的测试内容。而后import了待测智能合约。

  • 创建单元测试智能合约,根据合约不一样方法定义对应的test测试方法。

  • 方法体内部去调用待测智能合约的方法,传参接收返回值,而后使用关键字assert判断是否符合预期。

这是官方文档,详细说明如何使用Solidity来编写智能合约的单元测试。(http://truffleframework.com/docs/getting_started/solidity-tests)

2.3 编译合约

键入

truffle compile

输出状况:

image

输出结果

根据编译输出的路径地址./build/contracts,咱们去查看一下

image

产生文件列表

能够看到原来所在在contracts目录下的智能合约文件(有合约contract,有库library)均被编译成了json文件。

这些json文件就是truffle用来部署合约的编译文件。

2.4  配置以太坊本地环境

truffle.js是truffle的配置文件,启动好以太坊本地结点之后,咱们须要让truffle去识别它并使用它,这就须要在truffle.js中配置相关属性:

module.exports = { 

   networks: {    

      development: {      

         host: "127.0.0.1",      

         port: 8545,      

         network_id: "*" // Match any network id    

       }  

     }

};

【说明】若是不启动TestRPC,直接执行部署合约的话,会有如下错误提示:

image

无网络

2.5  启动本地以太坊客户端结点

启动适合开发的RPC客户端

启动以前安装好的EthereumJS RPC客户端。

testrpc

【说明】必定要启动一个新的客户端执行testrpc命令,能够观察到默认帐户和私钥信息。

image

本地客户端

2.6  部署合约

移植(migrate),对这里叫移植,但下面咱们仍使用“部署”这个词,truffle中部署的命令为:

truffle migrate

输出结果截图以下:

image

部署钱包的输出结果

查看testrpc的输出窗口,能够看到这笔交易和花费的区块:

image

2.7 测试合约

咱们知道在执行编译时会自动执行这些单元测试,若是有一个测试未经过则会中断编译过程。而在开发阶段,咱们也能够本身使用命令来测试。

truffle test

没有报错就说明经过了,绿条“5 passing(2s)”,有报错就会打印在下方。

image

输出截图1

image

输出截图2

3

用Truffle框架运行一个“Hello World!”智能合约

3.1 建立工程目录

返回父级目录,建立一个文件夹HelloWorld,来作为你的工程根目录。

mkdir HelloWorld

输入结果:

image

建立并进入该目录

3.2  初始化框架

在工做目录HelloWorld目录下,执行truffle初始化动做:

truffle init

输出截图:

image

初始化成功

采用SFTP下载文件到本地,可查看目录结构:

│  truffle-config.js

│  truffle.js

│   ├─contracts

│      Migrations.sol

│      

├─migrations

│      1_initial_migration.js

│       └─test

目录结构简单说明以下:

  • contract/ - Truffle默认的合约文件存放地址。

  • migrations/ - 存放发布脚本文件

  • test/ - 用来测试应用和合约的测试文件

  • truffle.js - Truffle的配置文件

3.3  新建新合约

在./contract目录下建立一个本身的合约文件Greeter.sol。

pragma solidity ^0.4.17;

contract Greeter        

 {    

      address creator;         

      string greeting;        

      function Greeter(string _greeting) public       

      {        

           creator = msg.sender;        

           greeting = _greeting;    

      }        

      function greet() public constant returns (string)               

      {        

            return greeting;    

      }        

      function setGreeting(string _newgreeting) public    

      {        

            greeting = _newgreeting;    

      }        

      /**********     

      Standard kill() function to recover funds      

      **********/       

      function kill()public    

      {         

           if (msg.sender == creator)            

               suicide(creator);  // kills this contract and sends remaining funds back to creator    

      }

}

3.4 新建发布脚本

在./migrations/目录下新建一个文件:2_deploy_contracts.js,增长发布代码。

var Greeter = artifacts.require("./Greeter.sol");

module.exports = function(deployer) {  

     deployer.deploy(Greeter,"Hello, World!");//"参数在第二个变量携带"

};

3.5 编译

进入到工程根目录./HelloWorld目录下,进行编译:

truffle compile

输出截图以下:

image

编译成功截图

3.6  启动你的客户端

若是以前没有启动RPC客户端的话,则须要启动以前安装好的EthereumJS RPC客户端。若是已启动的则忽略此步。

$ testrpc

3.7  部署合约(migrate)

执行部署命令(truffle migrate)提示出错。

truffle migrate

错误截图输出:

image

部署失败,提示网络未配置

修改文件./HelloWorld/truffle.js文件,增长网络配置:

module.exports = { 

     // See http://truffleframework.com/docs/advanced/configuration

    // to customize your Truffle configuration!     

       networks: {          

            development: {            

                   host: "localhost",            

                   port: 8545,           

                   network_id: "*" // 匹配任何network id         

             }    

     }

};

从新执行编译命令,从新执行部署命令(truffle migrate),则运行正确。对应Greeter的钱包地址为“0x7d62724f397a99613b84923a1166d683de2db680”

image

部署成功

3.8  TRUFFLE测试环境运行合约

Truffle提供了一种更加简单的方式,经过交互式控制台来与你的那些准备好的合约进行交互。

truffle console

一个基本的交互控制台,能够链接任何EVM客户端。若是你已经有了本身的ganache或者geth等EVM的本地环境,那么就可使用truffle console来交互,因此若是你已经有一个现成的小组共享的开发用EVM,那么使用这个没错。

truffle develop 一个交互控制台,启动时会自动生成一个开发用区块链环境(其实我认为它与ganache就是一个底层实现机制,都是默认生成10个帐户)。若是你没有本身的EVM环境的话,直接使用truffle develop很是方便。

truffle console

输入Greeter智能合约命令,显示打印出一个json结构,展现了它的各类属性内容。

image

查看Greeter结构

根据你的Greeter钱包地址,运行Greeter智能合约命令:

image

hello,world智能合约运行成功

3.8  GETH正式环境运行合约

启动GETH环境

本节假设GETH环境已安装好了。若是尚未安装的同窗,可参考文章《第一课 如何在WINDOWS环境下搭建以太坊开发环境》(https://www.jianshu.com/p/683ea7d62a39)的描述步骤。

而后在IDE内部打开一个terminal,启动GETH的EVM环境。

geth --datadir testNet3 --dev --rpc console

image

截图1

image

截图2

GETH 中是经过abi来注册合约对象的。

首先咱们找到./build/contracts/Greeter.json中的abi的value:

"abi": [    

      {      

        "inputs": [        

           {          

             "name": "_greeting",         

              "type": "string"       

           }      

        ],      

        "payable": false,     

        "stateMutability": "nonpayable",      

        "type": "constructor"    

        },   

        {      

         "constant": true,      

         "inputs": [],     

         "name": "greet",     

         "outputs": [       

            {         

              "name": "",          

              "type": "string"       

           }      

         ],      

        "payable": false,      

        "stateMutability": "view",     

        "type": "function"    

       },   

       {      

         "constant": false,      

         "inputs": [        

           {          

              "name": "_newgreeting",          

              "type": "string"        

           }      

         ],     

         "name": "setGreeting",     

         "outputs": [],     

         "payable": false,     

         "stateMutability": "nonpayable",     

          "type": "function"    

         },    

         {      

          "constant": false,      

          "inputs": [],      

          "name": "kill",      

          "outputs": [],     

          "payable": false,      

          "stateMutability": "nonpayable",     

          "type": "function"    

         }  

       ],

经过json压缩成一行获得:

var abi = [{"inputs": [{"name": "_greeting","type": "string"}],"payable": false,"stateMutability": "nonpayable","type": "constructor"},{"constant": true,"inputs": [],"name": "greet","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"name": "_newgreeting","type": "string"}],"name": "setGreeting","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": false,"inputs": [],"name": "kill","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"}];

从新部署智能合约到Geth环境

启动一个新的命令窗口,到

cd /usr/work/HelloWorld truffle migrate

成功部署输出截图:

image

智能合约部署成功

得到Greeter的地址为 0xb52bb3ce336f71a14345c78e5b2f8e63685e3f92

切换到GETH环境下,利用api和钱包地址(你本身Greeter智能合约的钱包地址哦)注册合约对象。

var abi = [{"inputs": [{"name": "_greeting","type": "string"}],"payable": false,"stateMutability": "nonpayable","type": "constructor"},{"constant": true,"inputs": [],"name": "greet","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"name": "_newgreeting","type": "string"}],"name": "setGreeting","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": false,"inputs": [],"name": "kill","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"}];

var HelloWorld = eth.contract(abi).at('0xb52bb3ce336f71a14345c78e5b2f8e63685e3f92')

HelloWorld.greet()

输出截图显示成功:

image

4

总结及参考

本文站在巨人的肩膀上,完成了以太坊开发框架Truffle从入门到实战的演示。对巨人的文章表示感谢:

1,Solidity的Truffle框架实战(手把手)

http://truffle.tryblockchain.org/Solidity-truffle-%E5%AE%9E%E6%88%98.html)

2, 【精解】开发一个智能合约

http://www.cnblogs.com/Evsward/p/contract.html)

3,官网参考:http://truffleframework.com/docs/

如需获取源码,请私信做者获取。(点击:“阅读原文”便可查看做者原文)

本文来源:简书

做者:笔名辉哥

如下是咱们的社区介绍,欢迎各类合做、交流、学习:)

image

相关文章
相关标签/搜索