什么是合约?
合约是代码(它的功能)和数据(它的状态)的集合,存在于以太坊区块链的特定地址。 合约账户能够在彼此之间传递信息,进行图灵完备的运算。合约依靠被称作以太坊虚拟机(EVM) 字节代码(以太坊特有的二进制格式)上的区块链运行。
合约很典型地用诸如Solidity等高级语言写成,然后编译成字节代码上传到区块链上。
另请参阅:
也存在其他语言, 尤其是Serpent和LLL,在此文本的以太坊高级语言章节会进一步阐述。去中心化应用开发资源列出了综合的开发环境,帮助你用这些语言开发的开发者工具,提供测试,和部署支持等功能。
以太坊高级语言
合约依靠被称作以太坊虚拟机(EVM) 字节代码(以太坊特有的二进制格式)上的区块链运行。然而,合约很典型地用诸如Solidity等高级语言写成,然后用以太坊虚拟机编译器编译成字节代码上传到区块链。下面是开发者可以用来为以太坊写智能合约的高级语言。
Solidity
Solidity是和JavaScript相似的语言,你可以用它来开发合约并编译成以太坊虚拟机字节代码。它目前是以太坊最受欢迎的语言。
- Solidity文本 – Solidity是以太坊的旗舰高级语言,用于写合约。
- Solidity在线实时编译器
- 标准合约API
- 有用的去中心化模式 – 用于去中心化应用开发的代码片段。
Serpent
Serpent是和Python类似的语言,可以用于开发合约编译成以太坊虚拟机字节代码。它力求简洁, 将低级语言在效率方面的优点和编程风格的操作简易相结合,同时合约编程增加了独特的领域特定功能。Serpent用LLL编译。- 以太坊维基百科上的Serpent
- Serpent以太坊虚拟机编译器
LLL
Lisp Like Language (LLL)是和Assembly类似的低级语言。它追求极简;本质上只是直接对以太坊虚拟机的一点包装。- GitHub上的LIBLLL
- LLL实例
Mutan (弃用)
Mutan是个静态类型,由Jeffrey Wilcke 开发设计的C类语言。它已经不再受到维护。
写合约
没有Hello World程序,语言就不完整。Solidity在以太坊环境内操作,没有明显的“输出”字符串的方式。我们能做的最接近的事就是用日志记录事件来把字符串放进区块链:
1234
contract HelloWorld { event Print(string out); function() { Print("Hello, World!"); } }
另请参阅:
Solidity docs里有更多写Solidity代码的示例和指导。
编译合约
solidity合约的编译可以通过很多机制完成。
- 通过命令行使用solc编译器。
- 在geth或eth提供的javascript控制台使用web3.eth.compile.solidity (这仍然需要安装solc 编译器)。
- 在线Solidity实时编译器。
- 建立solidity合约的Meteor dapp Cosmo。
- Mix IDE。
- 以太坊钱包。
在geth设置solidity编译器
如果你启动了geth节点,就可以查看哪个编译器可用。
12
> web3.eth.getCompilers();["lll", "solidity", "serpent"]
注意:solc编译器和cpp- ethereum一起安装。或者,你可以自己创建。
如果你的solc可执行文件不在标准位置,可以用—solc标志为solc可执行文件指定一个定制路线。
1
$ geth --solc /usr/local/bin/solc
1234
> admin.setSolc("/usr/local/bin/solc")solc, the solidity compiler commandline interfaceVersion: 0.2.2-02bb315d/.-Darwin/appleclang/JIT linked to libethereum-1.2.0-8007cef0/.-Darwin/appleclang/JITpath: /usr/local/bin/solc
编译一个简单合约
让我们编译一个简单的合约源:
1
> source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }"
你准备在geth JS控制台用eth.compile.solidity()编译solidity代码:
123456789101112131415161718192021222324252627282930
> contract = eth.compile.solidity(source).test{ code: '605280600c6000396000f3006000357c01000000000000000000000000000000000000000000000000000000 info: { language: 'Solidity', languageVersion: '0', compilerVersion: '0.9.13', abiDefinition: [{ constant: false, inputs: [{name: 'a', type: 'uint256' } ], name: 'multiply', outputs: [{name: 'd', type: 'uint256' } ], type: 'function' } ], userDoc: { methods: { } }, developerDoc: {methods: {} }, source: 'contract test { function multiply(uint a) returns(uint d) { return a * 7; } }' }}
下面的例子会向你展示如何通过JSON-RPC接合geth来使用编译器。
12
$ geth --datadir ~/eth/ --loglevel 6 --logtostderr=true --rpc --rpcport 8100 --rpccorsdomain ' * ' --mine console 2>> ~/eth/eth.log$ curl -X POST --data '{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["contract test {
- Code 编译的以太坊虚拟机字节代码
- Info 从编译器输出的额外元数据
- Source 源代码
- Language 合约语言 (Solidity,Serpent,LLL)
- LanguageVersion 合约语言版本
- compilerVersion 用于编译这个合约的solidity编译器版本。
- abiDefinition 应用的二进制界面定义
- userDoc 用户的NatSpec Doc。
- developerDoc 开发者的NatSpec Doc。
如果你的源包含多个合约,输出会包括每个合约一个入口,对应的合约信息对象可以用作为属性名称的合约名字检索到。你可以通过检测当前的GlobalRegistrar代码来试一下:
1
contracts = eth.compile.solidity(globalRegistrarSrc)
创建和部署合约
开始这一章节之前,确保你有解锁的账户和一些资金。 你现在会在区块链上创建一个合约,方法是用上一章节的以太坊虚拟机代码作为数据给空地址发送交易。注意:用在线Solidity实时编译器或Mix IDE程序会更容易完成。
1234
var primaryAddress = eth.accounts[0]var abi = [{ constant: false, inputs: [{ name: 'a', type: 'uint256' } ]var MyContract = eth.contract(abi)var contract = MyContract.new(arg1, arg2, ..., {from: primaryAddress, data: evmByteCodeFromPrevio
注意:注意arg1, arg2, …是合约构造函数参数,以备它要接受参数。如果合约不需要构造函数参数,就可以忽略这些参数。
值得指出的是,这一步骤需要你支付执行。一旦交易成功进入到区块,你的账户余额(你作为发送方放在from领域)会根据以太坊虚拟机的gas规则被扣减。一段时间以后,你的交易会在一个区块中出现,确认它带来的状态是共识。你的合约现在存在于区块链上。 以不同步的方式做同样的事看起来是这样:
123
MyContract.new([arg1, arg2, ...,]{from: primaryAccount, data: evmCode}, function(err, contract) { if (!err && contract.address) console.log(contract.address);});
与合约交互
与合约交互典型的做法是用诸如eth.contract()功能的抽象层,它会返回到javascript对象,和所有可用的合约功能一起,作为可调用的javascript功能。 描述合约可用功能的标准方式是ABI定义。这个对象是一个字符串,它描述了调用签名和每个可用合约功能的返回值。
12
var Multiply7 = eth.contract(contract.info.abiDefinition);var myMultiply7 = Multiply7.at(address);
1234
> myMultiply7.multiply.sendTransaction(3, {from: address})"0x12345"> myMultiply7.multiply.call(3)21
当用call被调用的时候,功能在以太坊虚拟机被本地执行,功能返回值和功能一起返回。用这种方式进行的调用不会记录在区块链上,因此也不会改变合约内部状态。这种调用方式被称为恒定功能调用。以这种方式进行的调用不花费以太币。
如果你只对返回值感兴趣,那么你应该用call 。如果你只关心合约状态的副作用,就应该用sendTransaction。
在上面的例子中,不会产生副作用,因此sendTransaction只会烧gas,增加宇宙的熵。
合约元数据
在之前的章节,我们揭示了怎样在区块链上创建合约。现在我们来处理剩下的编译器输出,合约元数据或者说合约信息。 当与不是你创建的合约交互时,你可能会想要文档或者查看源代码。合约作者被鼓励提供这样的可见信息,他们可以在区块链上登记或者借助第三方服务,比如说EtherChain。管理员API为所有选择登记的合约提供便利的方法来获取这个捆绑。
1234
// get the contract info for contract address to do manual verificationvar info = admin.getContractInfo(address) // lookup, fetch, decodevar source = info.source;var abiDef = info.abiDefinition
- 合约信息被可以公开访问的URI上传到可辨认的地方
- 任何人都可以只知道合约地址就找到是什么URI
要知道合约地址来查询url,获取实际合约元数据信息包,使用这一机制就足够了。
如果你是个尽职的合约创建者,请遵循以下步骤:
- 将合约本身部署到区块链
- 获取合约信息json文件
- 将合约信息json文件部署到你选择的任意url
- 注册代码散表 –>内容散表 –> url
// send off the contract to the blockchainMyContract.new({from: primaryAccount, data: contract.code}, function(error, contract){ if(!error && contract.address) { // calculates the content hash and registers it with the code hash in `HashReg` // it uses address to send the transaction. // returns the content hash that we use to register a url admin.register(primaryAccount, contract.address, contenthash) // here you deploy ~/dapps/shared/contracts/test/info.json to a url admin.registerUrl(primaryAccount, hash, url) }});
12345678910111213141516171819
source = "contract test { function multiply(uint a) returns(uint d) { return a * 7; } }" // compile with solccontract = eth.compile.solidity(source).test// create contract objectvar MyContract = eth.contract(contract.info.abiDefinition)// extracts info from contract, save the json serialisation in the given file, contenthash = admin.saveInfo(contract.info, "~/dapps/shared/contracts/test/info.json")
测试合约和交易
你通常需要低级的测试策略,为交易和合约排除故障。这一章节介绍了一些你可以用到的排错工作和做法。为了测试合约和交易而不产生实际的后果,你最好在私有区块链上测试。这可以通过配置一个替代网络ID (选择一个特别的数字)和/或不能用的端点来实现。推荐做法是,为了测试你用一个替代数据目录和端口 ,这样就不会意外地和实时运行的节点冲突(假定用默认运行。在虚拟机排错模式开启geth,推荐性能分析和最高的日志冗余级别 :
1
geth --datadir ~/dapps/testing/00/ --port 30310 --rpcport 8110 --networkid 4567890 --nodiscover -
123456
// create account. will prompt for passwordpersonal.newAccount();// name your primary account, will often use itprimary = eth.accounts[0];// check your balance (denominated in ether)balance = web3.fromWei(eth.getBalance(primary), "ether");
12345678910
// assume an existing unlocked primary accountprimary = eth.accounts[0];// mine 10 blocks to generate ether// starting minerminer.start(4);// sleep for 10 blocks (this can take quite some time).admin.sleepBlocks(10);// then stop mining (just not to burn heat in vain)miner.stop();balance = web3.fromWei(eth.getBalance(primary), "ether");
123
miner.start(1);admin.sleepBlocks(1);miner.stop();
123456
// shows transaction pooltxpool.status// number of pending txseth.getBlockTransactionCount("pending");// print all pending txseth.getBlock("pending", true).transactions
1234
txhash = eth.sendTansaction({from:primary, data: code})//... miningcontractaddress = eth.getTransactionReceipt(txhash);eth.getCode(contractaddress)
汪晓明
HPB芯链创始人,巴比特专栏作家。十余年金融大数据、区块链技术开发经验,曾参与创建银联大数据。主创区块链教学视频节目《明说》30多期,编写了《以太坊官网文档中文版》,并作为主要作者编写了《区块链开发指南》,在中国区块链社区以ID“蓝莲花”知名。
本文链接:
https://www.8btc.com/article/245926
转载请注明文章出处