精华内容
下载资源
问答
  • DApp智能合约系统开发,区块链智能合约app开发、开发找企鹅29五零60零微21,DApp智能合约软件开发、现成DApp智能合约模式系统、DApp智能合约开发搭建、区块链智能合约系统定制开发、DApp智能合约开发需求及费用。...

    DApp智能合约系统开发,区块链智能合约app开发、DApp智能合约软件开发、现成DApp智能合约模式系统、DApp智能合约开发搭建、区块链智能合约系统定制开发、DApp智能合约开发需求及费用。 
    区块链智能合约(Smartcontract)是一种特殊协议,旨在提供、验证及执行合约。具体来说,智能合约是区块链被称之为“去中心化的”重要原因,它允许我们在不需要第三方的情况下,执行可追溯、不可逆转和安全的交易。
       区块链智能合约包含了有关交易的所有信息,只有在满足要求后才会执行结果操作。智能合约和传统纸质合约的区别在于智能合约是由计算机生成的。因此,代码本身解释了参与方的相关义务。
        事实上,智能合约的参与方通常是互联网上的陌生人,受制于有约束力的数字化协议。本质上,智能合约是一个数字合约,除非满足要求,否则不会产生结果。


        智能合约如何运作?
        很多区块链网络使用的智能合约功能类似于自动售货机。智能合约与自动售货机类比:如果你向自动售货机(类比分类账本)
    转入比特币或其他加密货币,一旦输入满足智能合约代码要求,它会自动执行双方约定的义务。
        义务以“ifthen”形式写入代码,例如,“如果A完成任务1,那么,来自于B的付款会转给A。”通过这样的协议,智能合约允许各种资产交易,每个合约被复制和存储在分布式账本中。这样,所有信息都不能被篡改或破坏,数据加密确保参与者之间的完全匿名。
        虽然智能合约只能与数字生态系统的资产一起使用,不过,很多应用程序正在积极探索数字货币之外的世界,试图连接“真
    实”世界和“数字”世界。
        智能合约根据逻辑来编写和运作。只要满足输入要求,也就是说只要代码编写的要求被满足,合约中的义务将在安全和去信任的网络中得到执行。


    区块链智能合约是什么?
      智能合约的概念最初是由密码学家和计算机科学家Nick Szabo(曾是2017年万向区块链全球峰会的演讲嘉宾)于1993年提出的。随着它在区块链领域的运用,越来越为更多人所熟知。
      智能合约可以简单地定义为运行在区块链顶部的计算机代码。它包含一组规则,这些规则决定了相关方如何相互作用于彼此。只要满足预先制定的规则,协议就会自动执行。
    有趣的是,智能合约既不智能,又和现实生活中的法律协议不一样。智能合约“只能和编译它的人一样聪明”,换句话说,智能合约只是软件程序,与所有程序一样,完全按照程序员的意图执行。智能合约就像编程应用程序一样:“一旦出现,就去执行。”
      智能合约的执行原理是怎么样的呢?
      我们可以将制定好的合同条款以代码的形式写在区块链上,当某个要素触发了合约条款时,智能合约就会自动执行,为了让大家更好地理解智能合约,我们可以将其比作“自动贩卖机”,它能为你解决一切事情
      在未来,随着区块链技术的趋步发展,智能合约将应用于金融、服务、房地产、汽车、保险、信贷执法等领域。
      智能合约的最大价值在于有效提升交易效率,大大降低交易成本。

    展开全文
  • solidity智能合约中的匿名方法

    千次阅读 2018-03-07 17:27:13
    1.一个合约可以有一个匿名函数,此函数不能有参数,也不能有任何返回值,当我们企图去执行一个合约上没有的函数时,那么合约就会执行这个匿名函数。 2.当合约在只收到以太币的时候,也会调用这个匿名函数,而且一般...

    1.一个合约可以有一个匿名函数,此函数不能有参数,也不能有任何返回值,当我们企图去执行一个合约上没有的函数时,那么合约就会执行这个匿名函数。

    2.当合约在只收到以太币的时候,也会调用这个匿名函数,而且一般情况下会消耗很少的gas,所以当你接收到以太币后,想要执行一些操作的话,你尽可以把你想要的操作写到这个匿名函数里,因为这样做成本非常便宜。

    展开全文
  • 从传统到现代,身份管理都有不同的身份管理方式。 在现代时代,互联网作为一个平台,可以在需要时以数字形式共享它,这有其自身的缺点。... 本文详细介绍了如何在区块链上选择性地保持用户的属性匿名
  • 今天给大家聊聊智能合约账户之间的转账,并给出了代码。 发起合约方账户 接受合约账户 合约地址账户 匿名函数 这里我们了解一下solidity中的匿名函数。这个匿名函数主要用于转账。 ⼀个合约可以有且只有⼀个匿名...

    转账

    今天给大家聊聊智能合约账户之间的转账,并给出了代码。

    • 发起合约方账户
    • 接受合约账户
    • 合约地址账户

    匿名函数

    这里我们了解一下solidity中的匿名函数。这个匿名函数主要用于转账。

    • ⼀个合约可以有且只有⼀个匿名函数,此函数不能有参数,也不能有任何返回值,当我们企图去执行⼀个合约上没有的函数时,那么合约就会执⾏这个匿名函数。
    • 当合约在只收到以太币的时候,也会调⽤这个匿名函数,而且⼀般情况下会消耗很少的gas,所以当你接收到以太币后,想要执行⼀些操作的话,你尽可以把你想要的操作写到这个匿名函数⾥,因为这样做成本非常便宜。

    发起方给合约账户地址转账

    pragma solidity ^0.4.24;
    
    
    contract  demo{
    
    
        address public addr0 = 0x00ca35b7d915458ef540ade6068dfe2f44e8fa733c;
      
        //地址address类型本质上是一个160位的数字
    
        //可以进行加减,需要强制转换
        function add() public view returns(uint160) {
            return uint160(addr1) + 10;
        }
         
       
        //1. 匿名函数一般用来给合约转账,因为费用低
        //2. 这个匿名函数是由发起合约方对合约地址账户转账
        function () public  payable {
            
        }
        
        
        function getBalance() public view returns(uint256) {
            return addr1.balance;
        }
        
        
        function getContractBalance() public view returns(uint256) {
            //this代表当前合约本身
            //balance方法,获取当前合约的余额
            return address(this).balance;
        }
         
    }
    

    合约账户向一个地址进行转账

    pragma solidity ^0.4.24;
    
    
    contract  demo02 {
    
        address public addr1 = 0x0014723a09acff6d2a60dcdf7aa4aff308fddc160c;
        
    
        //地址address类型本质上是一个160位的数字
          //1. 匿名函数一般用来给合约转账,因为费用低
        //2. 这个匿名函数是由发起合约方对合约地址账户转账
        function () public  payable {
            
        }
        
        
        function getBalance() public view returns(uint256) {
            return addr1.balance;
        }
        
        
        function getContractBalance() public view returns(uint256) {
            //this代表当前合约本身
            //balance方法,获取当前合约的余额
            return address(this).balance;
        }
            //由合约向addr1 转账10以太币
        function transfer() public {
            //1 ether = 10 ^18 wei (10的18次方)
          
            addr1.transfer(10 * 10 **18);
        }
        
        //send转账与tranfer使用方式一致,但是如果转账金额不足,不会抛出异常,而是会返回false
        function sendTest() public {
            addr1.send(10 * 10 **18);
        }
        
    }
    
    

    注意:在solidity中tranfer转账函数,这个函数使用方法是向哪个address转账,就用这个address调用address.tranfer()(转账单位是“wei”)。这个转账是由当前合约账户余额向该地址转账。如果当前合约地址账户余额不足,会抛出异常。(但solidity转账函数还有一个send(),转账一般不用这个,因为合约账户余额不足进行转账,send函数不会抛出异常)。

    展开全文
  • 智能合约

    2018-05-18 15:52:37
    智能合约是电子化的,自动执行的,去中心化的,具有不可抵赖性,本质上它是一段代码,依托于区块链技术,它可以做很多事情,基于以太坊的智能合约可以让你的区块链扩展出任何你想要的功能。我相信,智能合约是区块链...
    合约
    合约也称合同、协议,是甲乙双方参与的,制定一系列条目规范双方权利与义务的文件。智能合约是电子化的,自动执行的,去中心化的,具有不可抵赖性,本质上它是一段代码,依托于区块链技术,它可以做很多事情,基于以太坊的智能合约可以让你的区块链扩展出任何你想要的功能。


    我相信,智能合约是区块链的未来,因为基于它能做的商业模型太多样了,远远不仅是数字货币一种。


    Solidity
    智能合约的编程语言是Solidity,扩展名为.sol,它是基于C++、JavaScript、Python创造而来的,这里是官方文档。


    Solidity是静态类型的,支持继承,有自己的函数库,它同样支持面向对象语言的自定义类型等其他功能。


    Solidity编写的智能合约代码运行在EVM,即以太坊虚拟机,正如java编写的代码运行在JVM一样,在同一个区块链中每一个结点的EVM都是相同的运行环境。通过智能合约,可以开发匿名投票、匿名拍卖、众筹以及多重签名的钱包等,以太坊每一个结点可以有多个账户,所以每个结点都可以称作钱包,可以管理名下的账户,以及转账、挖矿等操作。


    官方推荐IDE:Remix


    其实Solidity智能合约开发的IDE有很多,官方推荐的Remix是基于浏览器的,运行环境可以切换:


    挂在自己的JavaScript EVM
    也可以使用web3 provider
    还可以使用注入的web3连接到本机调试环境
    我使用以后,觉得浏览器的方式还是不习惯,尤其保存的文件无故消失,让我始终心有余悸,经过调研,下面我们将采用goLand,安装Intellij-Solidity-2.0.4插件的方式开发智能合约,然后使用Remix环境进行智能合约的部署。当然我们也可以使用Remix进行运行、测试以及调试工作,下面酌情展示。


    gas
    区块链中比较有意思的命名,相当于手续费但又有些不同。gas为天然气,用来代表我们程序运行所有的能耗,当发生交易等操作时会消耗相应的gas,gas的计算方式是


    gas 单价 × gas 数量


    其中gas单价是由用户,像我们这样的发起者愿意为此次操作付出多少以太币而定的(相当于你开车上路前愿意给你的油箱加多少油,假设你的油箱是无限大的)。gas数量是程序根据你操作的复杂度自动定义的。


    智能合约也是一样的,当一个发起者部署运行一段智能合约时,以太坊会收取gas费用,就像汽车行驶需要烧油一样,直到你的智能合约运行完毕,“油箱”中剩余的gas会退还给你,如果你的代码死循环了,耗尽了你“油箱”中的gas,那么以太坊会自动报出异常停止你的智能合约。我们在学习智能合约阶段,可以使用testnet环境来避免真的花费以太币。


    Dapp
    Dapp为Solidity提供了源码构建工具,包管理工具,单元测试以及智能合约部署,一会儿我们看看是否必须要用它。有时它也被称作去中心化的应用程序(Decentralized App)。这种应用程序除了有一段代码的智能合约以外,还需要UI,UE设计等,正如apple的app开发,我们未来的目标之一可以是开发自己的Dapp。


    准备工作
    首先要开启一个本地的EVM,前面的文章对Geth做了详细的介绍,这里直接启动一个本地开发模式的结点。


    geth --datadir testNet --dev console 2>>Documents/someLogs/testGeth.log


    简介一下geth的参数选项:


    dev
    Ephemeral proof-of-authority network with a pre-funded developer account, mining enabled


    短暂的认证证明网络,同时创建一个预存款很多钱的一个开发者账户,并自动开始挖矿。


    datadir
    datadir,指定结点文件目录,如果没有会自动创建一个,该目录包含:


    geth
    chaindata 区块数据、状态数据的目录,数据库是leveldb(一个键值对数据库)
    000001.log
    CURRENT 指向MANIFEST
    LOCK 区块数据锁定标识文件
    LOG 数据库(区块和状态)操作日志
    *.ldb 块数据文件
    MANIFEST-000000 (TODO,我也不知道是什么,谁能告诉我一下)
    LOCK 结点锁定标识文件
    nodekey 结点身份公钥,用于p2p网络寻找结点使用
    transactions.rlp
    geth.ipc Mist是以太坊钱包,该文件是Mist用来内部过程通信的socket文件。
    keystore 存储私钥
    UTC--2018-02-06T03-46-35.626115529Z--740b9c48d67cf333c8b1c0e609b6b90b40d3cdea
    以上目录中元素精解:


    ① nodekey
    结点之间相互寻找是通过一个发现协议:一个基于S/Kademlia的网络协议。这个协议会把包含IP地址的公钥联系起来。实际上在结点之间的peer连接使用的是一个完全不同的,加密的协议(RLPX)。RLPX加密的工作方式需要远程终端连接发起者的公钥作为身份识别。本质上来说,这个key链接了发现协议和RLPX。


    你可以随时删除这个nodekey,重启的时候会自动生成一个新的。


    ② keystore/UTC--2018-02-06T03-46-35.626115529Z--740b9c48d67cf333c8b1c0e609b6b90b40d3cdea
    这是存储结点私钥的位置,文件名为时间戳加上本地账户拼成的字符串。打开文件,内容为一个json,格式化以后为:


    {
        "address": "740b9c48d67cf333c8b1c0e609b6b90b40d3cdea", "comment":"本地账户地址", 
        "crypto": {
            "cipher": "aes-128-ctr", "comment":"加密协议采用的是AES-128",
            "ciphertext": "b331a3dbdde9abd14991116ac0bb1b742f22edda162b567974f8fbf1d694daef", "comment":"密文",
            "cipherparams": {
                "iv": "06d0df7a5b7160da852fbb01339149ae", "comment":"加密参数"
            }, 
            "kdf": "scrypt", "comment":"Key Derivation Function, 将短密码加盐hash成长密码,防彩虹表、防暴力破解",
            "kdfparams": {
                "dklen": 32, "comment":"KDF加密参数",
                "n": 262144, 
                "p": 1, 
                "r": 8, 
                "salt": "6ffbd23fac4ed386aac703bc180f50be02690bef5239057a34dde4dd4de2416b", "comment":"盐值,加盐加密"
            }, 
            "mac": "06b7d92b98a3b732dc1e63e7e09b8e3d79a9e8e1d43ee7a1b40482db295ea367", "comment":"message authentication code,消息认证码"
        }, 
        "id": "ff7e243a-150e-45f6-ac64-06b0ed2e68ec", "comment":"文件主键",
        "version": 3
    }
    这部分范畴属于密码学方面了,可以参考《应用密码学初探》


    ③ transactions.rlp
    RLP(Recursive Length Prefix),递归长度前缀。是以太坊中用于序列号对象的主要编码方法。根据文件名可以猜出,这是所有交易的序列化对象文件。


    ④ chaindata
    数据库采用leveldb,存储了区块数据以及状态数据。该目录下打包存储以.ldb为扩展名的每个区块的数据文件。每个块文件有容量的最大值,目前我本机默认的是2.1M,我们设想一下目前以太坊的区块高度为5039768,如果一个块是2.1M的话,那么整个区块链的数据大小为10TB。


    ⑤ leveldb
    Google出品的另一利器,使用C++编写,基于LSM(Log-Structured-Merge Tree)日志结构化合并树,是一个高效的键值对存储系统,是没有Sql语句的非关系型数据库。键值对均采用字符串类型,按照key排序。


    特点包括:


    键和值都是当作简单的字节数组,所以内容可以从ASCII字符串到二进制文件。
    数据按照key排序存储。
    调用者可以自定义一个比较方法来复写排序。
    基本操作有插入、获取和删除:Put(key,value), Get(key), Delete(key).
    一次原子批量操作可以执行多重变更操作。
    用户能够创建一个瞬时快照来获取一个统一的数据视图。
    数据可以向前亦或是向后迭代。
    数据采用Snappy(也是Google的一个压缩库)自动被压缩。
    用户可以通过一个虚拟接口自定义操作交互系统来实现一些额外的操作。
    局限性包括:


    无SQL,无索引,非关系型数据库
    同时只允许一个进程访问(但支持多线程)
    无客户端-服务端内置库支持,一个应用程序必须要包装自己的服务器到库才能获得这样的支持。
    console
    console命令在EVM启动的同时开启了一个交互控制台,后面的一串命令是将输出的log转存到文件testGeth.log中去,启动时的日志文件:


    WARN [02-06|11:46:35] No etherbase set and no accounts found as default 
    INFO [02-06|11:46:37] Using developer account                  address=0x740b9C48D67Cf333C8b1c0E609b6b90b40D3CdeA
    INFO [02-06|11:46:37] Starting peer-to-peer node               instance=Geth/v1.7.3-stable-4706005b/linux-amd64/go1.9.2
    INFO [02-06|11:46:37] Allocated cache and file handles         database=/home/liuwenbin/testNet/geth/chaindata cache=128 handles=1024
    INFO [02-06|11:46:37] Writing custom genesis block 
    INFO [02-06|11:46:37] Initialised chain configuration          config="{ChainID: 1337 Homestead: 0 DAO: <nil> DAOSupport: false EIP150: 0 EIP155: 0 EIP158: 0 Byzantium: 0 Engine: clique}"
    INFO [02-06|11:46:37] Initialising Ethereum protocol           versions="[63 62]" network=1
    INFO [02-06|11:46:37] Loaded most recent local header          number=0 hash=593c0e…256b90 td=1
    INFO [02-06|11:46:37] Loaded most recent local full block      number=0 hash=593c0e…256b90 td=1
    INFO [02-06|11:46:37] Loaded most recent local fast block      number=0 hash=593c0e…256b90 td=1
    INFO [02-06|11:46:37] Regenerated local transaction journal    transactions=0 accounts=0
    INFO [02-06|11:46:37] Starting P2P networking 
    INFO [02-06|11:46:37] started whisper v.5.0 
    INFO [02-06|11:46:37] RLPx listener up                         self="enode://ede08b763001ed3642e0b3860d57e694489bcc1f47dde8563f2577bdec48e6949748826d9b88f55f456af2ae1e75ce2ea04a59eb0ef1c2c53330be92e44e6515@[::]:46591?discport=0"
    INFO [02-06|11:46:37] Transaction pool price threshold updated price=18000000000
    INFO [02-06|11:46:37] IPC endpoint opened: /home/liuwenbin/testNet/geth.ipc 
    INFO [02-06|11:46:37] Starting mining operation 
    INFO [02-06|11:46:37] Commit new mining work                   number=1 txs=0 uncles=0 elapsed=53.048μs
    我们逐行分析,


    启动时第一行并未找到以太坊base的设置以及默认账户。
    说明使用了开发者账户,后面给出了账户地址。
    开始p2p网络结点,实例采用的是基于go1.9.2版本的geth实例。
    分配缓存和文件句柄(打开文件的唯一标识,给一个文件、设备、socket或管道一个名字,隐藏关联细节),数据库位置在/home/liuwenbin/testNet/geth/chaindata,缓存大小为128M, 文件句柄数为1024。
    写入当前创世块。
    初始化链配置,展示配置信息。
    初始化以太坊协议。
    载入大部分最近的本地数据头
    载入大部分最近的本地完整块数据
    载入大部分最近的本地最高块数据
    重新生成本地交易账本
    开始p2p网络
    开始whisper
    RLPx开始监控,并打印出当前enode信息
    交易池价格阀值更新,价格为=18000000000
    IPC端点开启:/home/liuwenbin/testNet/geth.ipc
    开始挖矿操作
    提交新的挖矿工作
    helloworld
    下面在console中查看一下当前账户的余额,发现开发环境默认给分配的余额太大,并不好测试,那么我们自己再创建一个用户,余额为0,然后用第一个“大款”账户转账给新创建用户1个以太币。


    > eth.sendTransaction({from: '0x740b9c48d67cf333c8b1c0e609b6b90b40d3cdea',to:'0x1d863371462223910a1f05329b6dea0b0f9c49f8',value:web3.toWei(1,"ether")})
    "0xb456244e4fb25b74108f05afe53670b5f1a857f5671e7d3fa2e221419d04382c"
    > eth.getBalance(eth.accounts[1])


    333333333333333333
    我发现一个事,之前乘三那个geth还存在呢(捂脸笑出泪),让我改一下吧。改后我重新部署了geth命令,然后将新建用户的3个以太转回大款账户,由于gas的存在(实际上即使转账时你自己指定,也是基于一个最小值,往多了给,如果低于这个最小值,就会报错:“你加的油太少啦,我根本跑不过去”。所以最终费了大力,让新账户保留下了


    > eth.getBalance(eth.accounts[1])
    79000
    这79000wei的以太币是无法转出去了,因为我的余额付不起油钱。实际上79000这个数字可读性还行,所以拿这个测试也可以。


    IDE编码
    上面说道了我们采用goLand安装Solidity插件的方式来开发智能合约。JetBrain系列IDE插件的安装我就不介绍了,网上随便查。下面我们开始编码:


    pragma solidity ^0.4.0;


    contract helloworld {
        string content;


        function helloworld(string _str) public {
            content = _str;
        }


        function getContent() constant public returns (string){
            return content;
        }
    }
    代码编写很简单,我们逐行解读:


    通过关键字pragma标识Solidity的版本为0.4.0,我们下面的代码都会采用该版本来编译。
    contract关键字定义一个合约,它可以有自己的方法,自己的属性(智能合约里面更愿意称为状态),将会存储在区块链中特定的地址。
    声明了一个字符串类型(注意首字母小写的类型关键字string)的content状态(叫做属性、成员变量都可以)
    通过关键字function定义一个构造方法,需要传入一个字符串数据,注意该方法的权限public被标识在了参数列表的后面。
    通过该方法赋值给状态content(注意不用使用this),方法的参数变量名采用了下划线开头的方式用来代表该变量的作用域很小,是私有变量,这是编程语言中的一种约定俗成的命名规则。
    通过关键字function定义一个打印方法,返回状态content的值,注意除了public权限以外,public的前侧还有一个constant关键字,后侧还通过关键字returns定义了返回值类型。
    部署
    上面我们使用了goLand的Solidity插件进行了合约代码的开发,然而该插件的功能仅包括:


    语法高亮,代码提示
    代码完整性检查
    文件模板
    goto声明
    Find usages
    代码格式化
    可以说都是针对编码辅助的操作,然而若我们要部署智能合约,还得回到Remix,我们新建一个sol文件,粘贴进去上面写好的helloworld代码,然后点击右侧Details,弹出的界面包含了名字、字节码、元数据等内容,我们只要其中的WEB3DEPLOY,复制出其中内容,将第一行传入参数“hello world”:


    var string_str = "hello world" ;
    var helloworldContract = web3.eth.contract([{"constant":true,"inputs":[],"name":"getContent","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"string_str","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"}]);
    var helloworld = helloworldContract.new(
       string_str,
       {
         from: web3.eth.accounts[0], 
         data: '0x6060604052341561000f57600080fd5b6040516102b83803806102b8833981016040528080518201919050508060009080519060200190610041929190610048565b50506100ed565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061008957805160ff19168380011785556100b7565b828001600101855582156100b7579182015b828111156100b657825182559160200191906001019061009b565b5b5090506100c491906100c8565b5090565b6100ea91905b808211156100e65760008160009055506001016100ce565b5090565b90565b6101bc806100fc6000396000f300606060405260043610610041576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806359016c7914610046575b600080fd5b341561005157600080fd5b6100596100d4565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561009957808201518184015260208101905061007e565b50505050905090810190601f1680156100c65780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6100dc61017c565b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156101725780601f1061014757610100808354040283529160200191610172565b820191906000526020600020905b81548152906001019060200180831161015557829003601f168201915b5050505050905090565b6020604051908101604052806000815250905600a165627a7a72305820f4bd9a6659a8625f89177c604c901764cf9cca4fa8aa2e792525da3647ca7a510029', 
         gas: '4700000'
       }, function (e, contract){
        console.log(e, contract);
        if (typeof contract.address !== 'undefined') {
             console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash);
        }
     })
    仔细观察上面的代码,Remix帮我们将代码转成了EVM可识别的样子,也就是将Solidity代码编译成web3的版本,其中也帮我们估算好了gas的金额,当我们执行这段合约时会自动扣掉我们余额中相应的数值作为gas费用。


    接着,我们回到console,先解锁智能合约发布者的账号,我们选择刚才新建的


    > personal.unlockAccount(eth.accounts[1],"lwb")
    true
    然后将上面的web3版的代码复制过来,回车,输出:


    Contract mined! address: 0x71db931bdb2f9516cf892aa0c620bd686d1095e5 transactionHash: 0x6e39a97dd2f260517bedeb9934cf88430526b46a379d5680cc092d8ea3f44602
    合约被挖出,打印出来了合约地址,交易hash(这在以太坊中也被认定为是一笔交易,我们付费gas给以太坊)。
    然后继续在console中输入


    > helloworld.getContent()
    "hello world"
    由于我们余额是79000,上面gas给预估的是4700000,所以预想结果是您的余额不足,合约无法运行,然而合约部署运行成功了。


    我们从大款那再转账一个以太币过来。然后关闭重启geth console,重复上面的操作。


    TODO: 余额仍旧未减少。不知道gas扣到哪去了。


    同步查看日志输出:


    INFO [02-06|17:36:34] Submitted contract creation              fullhash=0x6e39a97dd2f260517bedeb9934cf88430526b46a379d5680cc092d8ea3f44602 contract=0x71DB931bdb2f9516Cf892aA0c620bD686D1095E5
    INFO [02-06|17:36:34] Commit new mining work                   number=18 txs=1 uncles=0 elapsed=313.823μs
    INFO [02-06|17:36:34] Successfully sealed new block            number=18 hash=37913b…f101af
    INFO [02-06|17:36:34] ?? mined potential block                  number=18 hash=37913b…f101af
    每当我们提交了一个合约,


    第一行打印出了上面合约部署成功时的交易hash和合约地址。
    然后开始挖矿记账,目前已经记到第18个块了
    第三行第四行显示成功密封了一个新的区块。
    Solidity语法
    上面使用Solidity编写了一个helloworld智能合约,稍显力不从心,下面我们专门来学习一下Solidity语法,为未来我们编写复杂的智能合约工程打下基础。


    类型
    学习一门新的编程语言,首先要看它的类型,Solidity是静态类型语言,跟java一样,也就是说在编译之前都要指定好每个变量的具体类型。类型可以分为值类型和引用类型,与java类似。


    1.值类型
    值类型作为参数时永远传的是值,每一次入参出参都是内存中值的副本。包括:


    布尔类型bool,true\false,与非门操作不多说。
    整型int,与go语言相同,有符无符int/uint,从长度8到256(int8, int16, int32... int256),数学运算包括位运算不多讲,有不明的地方请转到掌握一门语言Go。
    定长浮点型fixed,有符无符fixed/ufixed,还未完全被Solidity支持,可声明不可赋值,多说无益。
    定长字节数组(byte/bytes1, bytes2, bytes3, ..., bytes32),没什么好说的,有个length属性可以取出长度。
    变长字节数组(bytes, string),与以上定长类型不同的是,变长类型不必预先指定长度,但bytes和string都属于引用类型,下面会具体介绍。
    地址类型address,根据以太坊结点、账户、合约等address的概念设计,长度限制为20字节。神奇的是,address封装好了一个balance属性,可以查看账户余额,以及transfer方法,可以直接转账,非常方便。此外它还有send、call等很多常用方法,这是Solidity封装好的一个基本类型,适用于智能合约开发,以后用到了再详细探究细节。
    枚举类型enum,例如“enum ColorEnums {Red, White, Black}”,注意返回的都是下标值,Red会返回0,White返回1,Black返回2。
    函数类function,变量可以作为其他function的参数,也可以作为其他function的返回值。方法在参数后面可以声明函数可见性,除了public(任意合约)和private(当前合约)以外,还有internal(当前合约以及继承合约)和external(仅外部访问)。external是由address和function签名组成,可作为外部调用函数的参数或者返回值,默认情况无显式声明时就是internal。function还需要声明返回值类型,returns (type),但若方法无返回值时要省略这个部分。另外还有特殊的部分是在internal和returns中间还可以加入一个配置属性,[pure|constant|view|payable]。constant标识了一个常量不会被更改,只读不可写入。view是查看区块链上的数据的意思,比constant更加准确地表达了只是看看,不做修改的意图 。pure是纯函数的意思,就是保证不读取和写入到区块链内存的函数。payable是声明了该函数设计支付操作,需要虚拟机提供事务支持。 protected,
    下面是针对以上类型的字面量类型:


    字面量是一种针对某种值的表示法,简单来说,就是变量赋值时必须是等号右边的部分。


    Address字面量,十六进制字面量的一种特殊情况:长度在为40个十六进制数(一个字节8位可存储两个十六进制数,一个4位),且通过了address checksum 校验。
    有理数整型字面量,整数,小数,科学计数法2e10,最广泛的字面量类型。
    字符串字面量,单引号、双引号均可的字符串。
    十六进制字面量,hex开头,例如hex"001122FF",必须是一个字符串,内容必须是十六进制。
    2.引用类型
    数据位置类型,分为memory(内存-临时)和storage(区块链-永久),通过在变量名前声明memory还是storage来定义该变量的数据位置。一般来讲,函数参数默认为memory,局部复杂类型(作用域为局部)以及状态变量(作用域为全局)属于storage类型。还有一个calldata与memory差不多,专门用于存储函数参数的,也不是永久存储。额外提一点,EVM的memory是基于stack的,stack可以临时存储一些小的局部变量。这些变量存储消耗的gas是不同的,storage最大,memory较小,stack几乎免费,calldata与memory差不多。
    数组类型Arrays,长度可定可变,可以存储于storage和memory,元素类型可以是任何类型,但memory时不能是映射类型(就是键值对类型)。
    结构体struct,与Go语言相同的设定,自定义类型,使用方式也与Go极为相似。
    mapping类型
    mapping类型就是键值对,现在最新语言都会给自身增加键值对数据结构的封装支持。mapping的声明方式为:


    mapping(_KeyType => _ValueType)
    键值对中间通过一个“=>”连接。元素内容,Solidity类型均可,与其他键值对使用差不多,遇到问题再深入研究。


    其他
    关于Solidity其他语法这里暂不过多介绍,掌握以上Solidity的类型知识,我想其他语法可以在实战中解决掉。下面会以“Solidit语法补充说明”的形式对新遇到的语法问题进行补充研究。


    Truffle MetaCoin环境搭建实例
    上面我们开发部署运行智能合约helloworld时,编码是在goLand,编译是在Remix,部署运行是在geth console,感觉好混乱,也不适合大规模工程开发,是否有一种工具可以集成这一切?


    Truffle!


    准备工作
    由于truffle是依赖于nodejs,可能会有版本不兼容的问题,因此要先完全删除你机器上的nodejs和npm,然后再安装纯净版的nodejs,npm,truffle,请按照以下命令进行。


    sudo apt-get remove nodejs
    sudo apt-get remove npm
    sudo apt-get update
    which node
    wget https://nodejs.org/dist/v8.8.0/node-v8.8.0-linux-x64.tar.gz
    sudo tar -xf node-v8.8.0-linux-x64.tar.gz --directory /usr/local --strip-components 1
    node --version
    npm --version
    sudo npm install -g truffle
    MetaCoin初始化
    此时应该可以直接使用命令truffle了,下面我们建立一个工作间truffle-workspace,然后在工作间执行:


    mkdir MetaCoin
    cd MetaCoin
    truffle unbox metacoin
    原来使用truffle init,但现在它存在于unbox。


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


    Truffle的官方Boxes地址


    可以看到,现在官方盒子还不多,总共7个,有三个是关于react的,两个是truffle自己的项目,可以下载体验,剩下两个是我们比较关心的,一个是metacoin,非常好的入门示例,另一个是webpack,顾名思义,它是一套比起metacoin更加完整的模板的存在。既然我们是初学,下面我们就从metacoin入手学习。


    目录结构
    进入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.17;


    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来编写智能合约的单元测试。


    初始化文件解释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来编写智能合约的单元测试。


    编译合约
    键入


    truffle compile
    输出情况:


    liuwenbin@liuwenbin-H81M-DS2:~/work/truffle-workspace/MetaCoin$ truffle compile
    Compiling ./contracts/ConvertLib.sol...
    Compiling ./contracts/MetaCoin.sol...
    Compiling ./contracts/Migrations.sol...
    Writing artifacts to ./build/contracts
    根据编译输出的路径地址./build/contracts,我们去查看一下


    liuwenbin@liuwenbin-H81M-DS2:~/work/truffle-workspace/build/contracts$ ls
    ConvertLib.json  MetaCoin.json  Migrations.json
    可以看到原来所在在contracts目录下的智能合约文件(有合约contract,有库library)均被编译成了json文件。


    这些json文件就是truffle用来部署合约的编译文件,这与上面通过Remix编译的WEB3DEPLOY的js代码段不同。


    部署合约
    移植,对这里叫移植,但下面我们仍使用“部署”这个词,truffle中部署的命令为:


    truffle migrate
    这里遇到的问题较多,我来一一解决:


    问题1:启动本地以太坊客户端结点
    以太坊客户端有很多,truffle自己就有一个ganache,但我没安装成功,下面列举一下:


    Geth (go-ethereum)也就是我们之前到现在一直在介绍的。
    WebThree (cpp-ethereum): C++版本,我们一直在使用的是上面的Go版本。
    Parity
    More: https://www.ethereum.org/cli
    当然了,我们还是继续使用geth,仍旧使用上面介绍过的启动命令启动


    geth --datadir testNet --dev console 2>>Document/someLogs/testGeth.log
    问题2:配置truffle.js
    上文说到了,truffle.js是truffle的配置文件,启动好以太坊本地结点以后,我们需要让truffle去识别它并使用它,这就需要在truffle.js中配置相关属性:


    module.exports = {
      networks: {
        development: {
          host: "127.0.0.1",
          port: 8545,
          network_id: "*" // Match any network id
        }
      }
    };
    问题3:修改geth启动命令与truffle.js配置文件
    以上两个问题解决以后,我们使用truffle migrate来部署,terminal报错:


    liuwenbin@liuwenbin-H81M-DS2:~/work/truffle-workspace/MetaCoin$ truffle migrate
    Could not connect to your Ethereum client. Please check that your Ethereum client:
        - is running
        - is accepting RPC connections (i.e., "--rpc" option is used in geth)
        - is accessible over the network
        - is properly configured in your Truffle configuration file (truffle.js)
    错误信息很清楚,直接增加一个参数--rpc,最终修改我们的启动命令为:


    geth --datadir testNet --dev --rpc console 2>>Document/someLogs/testGeth.log
    继续使用truffle migrate来部署,terminal及继续报错:


    Error: exceeds block gas limit
    去truffle github issues中查找,找到一行解决办法,粘贴如下:


    Possibility: you're giving the transaction too high of a gasLimit. If the transaction has a limit of 2,000,000, it'd stop you since it could theoretically go over the block gas limit, even if in practice it won't. If this is the case, see if you can reduce the transaction's gasLimit while remaining above the amount it actually needs--that might do the trick.


    好,我们再修改一下truffle.js如下:


    module.exports = {
      networks: {
        development: {
          host: "127.0.0.1",
          port: 8545,
          network_id: "*", // Match any network id
          gas:500000
        }
      }
    };
    继续执行truffle migrate,执行成功。


    liuwenbin@liuwenbin-H81M-DS2:~/work/truffle-workspace/MetaCoin$ truffle migrate
    Using network 'development'.


    Running migration: 1_initial_migration.js
      Deploying Migrations...
      ... 0x2adf8c421a2814ea4d5f1a211048ac64c47f6fcf64a1418dd4abc463d604d8fc


    此时terminal处于监听状态,我们先不管他,下面请转到“IDE cooking steps”章节会给出解释。


    去看一下Documents/someLogs/testGeth.log文件:


    INFO [02-08|14:59:39] Submitted contract creation              fullhash=0x2adf8c421a2814ea4d5f1a211048ac64c47f6fcf64a1418dd4abc463d604d8fc contract=0xc8B95403276e5B4482718803C25A449743d59755
    INFO [02-08|14:59:39] Commit new mining work                   number=23 txs=1 uncles=0 elapsed=351.917μs
    INFO [02-08|14:59:39] Successfully sealed new block            number=23 hash=b97b83…b19548
    INFO [02-08|14:59:39] ?? mined potential block                  number=23 hash=b97b83…b19548
    我截取到了日志文件中以上的部分,可以看到,我们的智能合约已经被成功部署了,且日志中的hash值与上面监听状态的terminal中显式的是相同的,说明是一致的。下面我们就可以在终端使用该智能合约了。


    测试合约
    上面我们介绍了智能合约的单元测试的写法,包括js版本和Solidity版本,我们也知道在执行编译时会自动执行这些单元测试,如果有一个测试未通过则会中断编译过程。而在开发阶段,我们也可以自己使用命令来测试。


    truffle test
    没有报错就说明通过了,绿条,有报错就会打印在下方。


    使用truffle开发智能合约
    经过上面truffle metacoin环境模板的搭建,我们整个智能合约的开发、编译、部署以及运行环境就搭建好了。下面我们用这套环境来重现最初的helloworld智能合约。


    创建工程
    首先创建我们的工程Helloworld:


    liuwenbin@liuwenbin-H81M-DS2:~/work/truffle-workspace$ mkdir helloworld && cd helloworld
    liuwenbin@liuwenbin-H81M-DS2:~/work/truffle-workspace/helloworld$ truffle init
    Downloading...
    Unpacking...
    Setting up...
    Unbox successful. Sweet!


    Commands:


      Compile:        truffle compile
      Migrate:        truffle migrate
      Test contracts: truffle test
    liuwenbin@liuwenbin-H81M-DS2:~/work/truffle-workspace/helloworld$ ls
    contracts  migrations  test  truffle-config.js  truffle.js
    IDE cooking steps
    仍旧使用goLand + Solidity插件
    *导入项目truffle-workspace/helloworld
    在contracts目录下新建一个智能合约文件Helloworld.sol(注意要与工程名相同,同时最好都首字母大写),输入与上文相同的内容。
    *然后在IDE内部打开一个terminal,启动EVM


    liuwenbin@liuwenbin-H81M-DS2:~$ geth --datadir testNet --dev --rpc console
    *将上面的truffle.js配置内容粘贴到当前工程的truffle.js配置文件中
    新增一个helloworld的部署文件“2_deploy_helloworld.js”到migrations目录下,添加对helloworld智能合约的部署配置。
    再开启一个terminal,执行truffle compile, truffle migrate。


    WARN: 这一步遇到问题,上面所谓监听状态实际上是卡住了,我们的智能合约并未部署成功,虽然在EVM中已经写入了块,但是无法识别该合约对象。理想状态下我们可以调用合约对象了,这个流程就全通了,但是没事,我去继续查一下解决方案。


    采用客户端ganache代替geth
    上文说明了这些原因,我也在官网下载了ganache,这是一个AppImage文件,这个文件在linux系统可以直接启动,首先我们需要将它的执行权限修改一下,然后启动即可。


    chmod a+x exampleName.AppImage
    启动以后,可以看到这个界面。
    image


    很丰满。


    我想到一个事情,这里重申一下:我目前的测试开发环境,如果没有交易产生,挖矿不会自动进行。对于比特币和以太坊的正式环境来说,他们会限制出块时间,因为现在他们的交易量都很大,交易就会被拖慢,而不会产生没有交易,到了固定时间就要出个空块的情况。不过也有特例,因为共识算法加上对出块时间的限制,是有可能出现空块的。这很浪费,不过就我目前来看,算是留个思考题吧。


    我们应该都可以直观的看懂,然后我们将它的网络配置到工程的truffle.js中去。


    我们仍旧可以使用命令“geth attach http://localhost:7545” ,从geth命令行attach到这个ganache EVM网络中去。


    truffle migrate
    配置完成后,继续执行以上命令,可以看到不再发生以上被卡住的情况了,但是不识别我的Helloworld智能合约:


    Error: Could not find artifacts for Helloworld.sol from any sources
    继续探索...


    解决方案:居然是我的contract 名字不匹配的原因,因为我当时想统一将工程名、合约文件名都改为首字母大写,但忘记该合约文件内部的contract后面的名字了,以及构造函数,这就像你改了java的类文件名,但没有该内部类名一样,可惜goland的Solidity插件并未报错啊,害的我找了半天,不过以后还是要靠自己多注意了。


    但是,仍然有问题:


    Error encountered, bailing. Network state unknown. Review successful transactions manually.
    应该是truffle.js中网络配置的问题。


    继续探索...


    解决方案:哥们定睛一看,在上面这个表明看起来的error面前,不要先入为主,下面还有一行报错信息:


    Error: Helloworld contract constructor expected 1 arguments, received 0
    原来是我的合约内部有问题,我们通过truffle部署的时候不知道如何去给构造函数赋值,当时我们使用Remix的时候是手动修改的WEB3DEPLOY的js代码段,这里我就直接在合约代码中修改吧,最后是这样:


    pragma solidity ^0.4.0;


    contract Helloworld {
        string content;


        function Helloworld() public {
            content = "hello, world!";
        }


        function getContent() constant public returns (string){
            return content;
        }
    }
    多谢博友moqiang02的友情提示,这里可以在部署时进行构造函数的赋值,不必修改智能合约内容:在2_deploy_contracts.js中,修改deploy脚本,“deployer.deploy(Helloworld,"hello, world!");”即可。下面所有流程不影响,继续


    truffle migrate!


    liuwenbin@liuwenbin-H81M-DS2:~/work/truffle-workspace/Helloworld$ truffle migrate
    Using network 'development'.


    Running migration: 2_deploy_contracts.js
      Deploying Helloworld...
      ... 0x391f2c060b1f9cbe7b42493fc858ffa455d40f6e9af754a105092a9ac32e53c3
      Helloworld: 0x2c2b9c9a4a25e24b174f26114e8926a9f2128fe4
    Saving successful migration to network...
      ... 0x0e8fab8924d93f0b17aa1c9dc58b976089a61e4debcd185dffa2c16e5cc539e9
    Saving artifacts...
    liuwenbin@liuwenbin-H81M-DS2:~/work/truffle-workspace/Helloworld$ 
    成功!


    对比ganache日志来看:


    [5:24:49 PM]   Transaction: 0x391f2c060b1f9cbe7b42493fc858ffa455d40f6e9af754a105092a9ac32e53c3
    [5:24:49 PM]   Contract created: 0x2c2b9c9a4a25e24b174f26114e8926a9f2128fe4
    [5:24:49 PM]   Gas usage: 205611
    [5:24:49 PM]   Block Number: 7
    [5:24:49 PM]   Block Time: Thu Feb 08 2018 17:24:49 GMT+0800 (CST)
    [5:24:49 PM]   Transaction: 0x0e8fab8924d93f0b17aa1c9dc58b976089a61e4debcd185dffa2c16e5cc539e9
    [5:24:49 PM]   Gas usage: 26981
    [5:24:49 PM]   Block Number: 8
    [5:24:49 PM]   Block Time: Thu Feb 08 2018 17:24:49 GMT+0800 (CST)
    可以看到我们通过truffle部署一个智能合约,要提交两个块,有两笔交易产生。为什么呢?


    因为第一笔交易是来自与Helloworld.sol的创建,第二笔交易是来自于migration,每次部署一个新的合约都要执行这两步。


    部署成功以后,我们可以得到合约的地址:0x2c2b9c9a4a25e24b174f26114e8926a9f2128fe4,后面会使用这个地址来实现与合约的交互。


    与合约交互
    geth 方式
    此时如果我们直接geth attach到ganache本地环境中,无法与合约实现交互。因为目前虽然我们在EVM中创建了一个合约,但未在基于web3js的geth中注册合约对象,


    geth 中是通过abi来注册合约对象的。


    首先我们找到build/contracts/Helloworld.json中的abi的value,通过json压缩成一行,


    abi = [{"inputs":[{"name":"con","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"arg","type":"string"}],"name":"GetGreeting","type":"event"},{"constant":true,"inputs":[],"name":"getContent","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"}]
    然后注册合约对象:


    hello = eth.contract(abi).at('0x2c2b9c9a4a25e24b174f26114e8926a9f2128fe4')
    对象注册成功以后,就可以像正常合约那样去调用了。


    > hello.getContent()
    "hello, world!"
    truffle console
    truffle框架没有直接使用abi,而是为我们封装提供了更加方便的调用方式。


    truffle console,一个基本的交互控制台,可以连接任何EVM客户端。如果你已经有了自己的ganache或者geth等EVM的本地环境,那么就可以使用truffle console来交互,所以如果你已经有一个现成的小组共享的开发用EVM,那么使用这个没错。
    truffle develop,一个交互控制台,启动时会自动生成一个开发用区块链环境(其实我认为它与ganache就是一个底层实现机制,都是默认生成10个账户)。如果你没有自己的EVM环境的话,直接使用truffle develop非常方便。
    我虽然希望能够得到大一统的简单编写的开发测试环境,但是我并不愿意使用develop模式,下面我们使用console模式来与刚刚部署的Helloworld智能合约进行交互。


    truffle console
    执行以后,我们可以敲出Helloworld了,打印出一个json结构,展示了它的各种属性内容。它是一个TruffleContract,内容非常多。


    tip: 上面提到过Solidity的event语法,里面展示了如果针对未使用event的智能合约,要通过var returnValue = exampleContract.foo.call(2);// 通过web3 的message的call来调用。


    我们的Helloworld合约并未使用event方法,所以让我尝试一下这种方式来调取:


    truffle(development)> Helloworld.at("0x2c2b9c9a4a25e24b174f26114e8926a9f2128fe4").getContent.call()
    'hello, world!'
    此刻的心情真是扬眉吐气,从来没有一次这么艰难的“helloworld”历程!


    调试合约
    truffle debug我还没来得及体验,先使用Remix吧,等我日后体验完觉得它不错我再来补充。Remix的debug其实还不错,不过很多人好像用不明白。我这里简单介绍一下吧,当你编写完一个智能合约以后,一般它会自动帮你编译,并且会在下方展示出你的属性,方法(如果没有的话,请尝试去交易的位置把交易和gas配置一下即可),然后点击其中你想调试的方法(注意入参),在控制台会打印出它的执行过程,同时右侧会有一个“debug”的小按钮,点击它(注意要预先在代码中设置断点),然后就可以按行调试了,随着一行行的运行,属性变量的值也会有所改变。
    展开全文
  • 距离去年九四周年祭的日子越来越近了,国内对币圈政策在逐渐锁紧,这不继腾讯...所以今天咱们换个口味,今天给大家介绍下区块链技术依附的智能合约。 我们经常会听到区块链技术的流行语,如“去中心化网络”“智...
  • 使用nodejs编译智能合约 nodejs环境搭建,自行百度解决 新建工程 新建项目工程文件夹,进入文件夹 在`cmd`里使用`npm install solc` 智能合约,以下提供一个简单的智能合约 使用nodejs编译智能...
  • 2.熟悉以太坊智能合约的部署和使用流程; 3.了解以太坊智能合约的应用和常见安全性问题; 参考: https://www.jianshu.com/p/3aa80732ed73 https://blog.csdn.net/Jade0214/article/details/80043776 ...
  • 智能合约漏洞攻击事件Applications on Ethereum manage financial value, making security absolutely crucial. As a nascent, experimental technology, smart contracts have certainly had their fair share of ...
  • 以太坊中的智能合约

    万次阅读 2018-08-12 15:23:38
    以太坊中的智能合约(Smart Coantract) 创建智能合约 以太坊中的智能合约是运行在区块链上的一段代码,代码的逻辑定义了合约的内容。只能合约是以太坊和比特币系统最大的区别。在以太坊中,智能合约的账户保存...
  • 智能合约,英文是 smartcontract,作为在区块链上运行的计算机代码,当满足事先设定的条件时会自动触发执行。 智能合约就是能够使区块链技术应用到现实当中的应用层,几乎所有类型的金融交易都可以被改造成在区块链...
  • 虽然智能合约仍然处于初始阶段,但是其潜力显而易见。想象一下,分配你的遗产就像滑动可调滑块就能 决定谁得到多少遗产一样简单。如果开发出足够简单的用户交互界面,它就能够解决许多法律难题,例如 更新遗嘱。...
  • eg.contract A{ function A { } /*下面的函数就是匿名函数*/ function () { }}2、一个合约可以有一个匿名函数,函数无参数和返回值,执行合约上一个没有的函数时,合约会执行该函数3、当合约只收到以太币的时候...
  • 根据谷歌趋势数据显示,目前,程序员对智能合约编程的兴趣已经处于历史最高水平,其中中国高居全球榜首,随着区块链技术的发展,相信日后智能合约将会与我们的生活密切相关,今天就为大家介绍一下什么是智能合约。...
  • 链乔教育在线|智能合约学习——以太坊智能合约学习笔记(四) 本笔记记录的是使用matemask钱包连接以太坊私链,并编写一个基本的提币智能合约部署到以太坊私链上,进行调用。 一、浏览器安装metamask插件 由于google...
  • 智能合约问题总结

    2021-04-22 16:13:27
    创建一个交易,接收地址为要调用的那个智能合约的地址,data域填写要调用的函数以及参数的编码值。 合约账户可以发起交易吗? 不可以。一个交易只能由外部账户发起,合约账户不能够发起交易。 一个合约如何调用另一...
  • ETH-22智能合约

    2020-05-03 02:47:21
    内容整理自 北京大学肖臻老师《区块链技术与应用》公开课 ETH-22-智能合约 智能合约是以太坊的精髓,也是以太坊和比特币的主要区别。 智能合约的本质是运行在区块链上的一段代码,代码的逻辑定义了合约的内容。 ...
  • 以太坊智能合约的原理和使用方法

    千次阅读 2020-02-11 22:01:37
    一、智能合约概述 1. 智能合约是什么 智能合约本质上是运行在区块链上的一段代码,代码的逻辑定义了合约的内容。 智能合约账户保存了合约当前的运行状态,包括当前余额(balance)、交易次数(nonce)、合约代码...
  • 智能合约的安全

    2018-12-13 12:36:00
    智能合约的安全问题一直是编写智能合约的关键点。多数的智能合约都是开源的,源码公布更容易被黑客找到攻击的漏洞。这里将一些常见的,易犯的错误。首先我们先看看下面这段代码: contract text{ address owner;...
  • 智能合约又称智能合同,是由事件驱动的、具有状态的、获得多方承认的、运行在区块链之上的、且能够根据预设条件自动处理资产的程序,智能合约最大的优势是利用程序算法替代人仲裁和执行合同。 简单说,智能合约是一...
  • MDF智能合约之全面解读

    千次阅读 2020-12-09 16:11:29
    MDF智能合约怎么玩?MMMDeFi互助靠谱吗?------非凡哥为你解析(薇 ffgsbb) MDF系统的解读-MDF全球运营中心 成长是一种日渐的雕琢,在每次砸下那把人生的刻刀时,你都会感觉到痛,那些你现在看起来已经荒诞可笑的...
  • 区块链学习笔记21——智能合约

    千次阅读 2021-02-28 19:31:49
    二十一、智能合约 智能合约是以太坊的精髓,也是以太坊和比特币一个最大的区别。 什么是智能合约智能合约的本质是运行在区块链上的一段代码,代码的逻辑定义了智能合约的内容 智能合约的账户保存了合约当前的...
  • 一文看懂InterValue智能合约

    千次阅读 2019-03-28 11:57:42
    以太坊的出现给区块链世界增添了不少颜色,以太坊的智能合约技术,让区块链网络上可以运行去中心化的应用。但是速度依然是个大问题,以太坊已经被证明不能被扩展,虽然V神团队依然在努力。 而EOS又给我们带来了惊喜...
  • 智能合约开发其实是一种计算机协议,用一段计算机指令实现自我验证、自动执行,并产生可以验证的证据来证明合约操作的有效性。 当智能合约的双方在区块链上产生资产交易的时候就会自动触发一段代码来自动完成具体的...
  • 智能合约 智能合约是在区块链上运行的应用或者程序。通常情况下,智能合约是一组具有特定规则的数字化协议,且这些协议能够强制化执行。这些规则由计算机代码预先定义,所有节点会复制和执行这些计算机源码。 智能...
  • 第四章:以太坊智能合约 - 合约 合约第四章:以太坊智能合约 - 合约合约概述创建合约访问函数(Getter Function)函数修改器(Function Modifier)多个函数修改器理解修改器的执行次序状态常量视图函数(View Function)...
  • 详解区块链的智能合约

    千次阅读 2018-08-28 10:58:00
    智能合约的历史 在全面阐述什么是智能合约之前,我们先回顾一下它创建的历史背景,有助于理解它的重要性。 1994年,计算机科学家和密码学家Nick Szabo首次提出“智能合约”概念。它早于区块链概念的诞生。Szabo...
  • 傻瓜智能合约 (Smart Contracts for Dummies) 如果您仍然不了解智能合约,那么…… (If you still don’t get what the heck a Smart Contract is…) Ok, you know a bit about Bitcoin (see: ...
  • 什么是智能合约智能合约是一种计算机化的代码,用于执行预定义的合同条款。这些契约自行执行它们自己与存在于分布式分散区块链网络中的协议条款。它们允许在不同的匿名当事人之间执行可信的交易,而不用中央当局...
  • 智能合约文档

    2018-09-06 17:52:00
    solidity文档审阅 特点 基于底层账户而非utxo 所以有一个特殊的地址 定位于用户 定位于合约 合约本身是一个账户 语言内嵌框架支持支付 只需要一个...运行环境是在p2p网络,会比较强调合约或者函数执行的方式,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,014
精华内容 1,605
关键字:

匿名智能合约