以太坊中的智能合约

article/2025/9/13 5:28:55

以太坊中的智能合约(Smart Coantract)

创建智能合约

以太坊中的智能合约是运行在区块链上的一段代码,代码的逻辑定义了合约的内容。合约的账户保存了合约当前的运行状态,主要包含了4部分内容。

  • balance:当前余额
  • nonce: 交易次数
  • code: 合约代码
  • storge: 存储,是一棵MPT

智能合约一般使用Solidity语言进行编写,语法上与JavaScript相似。如下是一段Solidity编写的智能合约的代码,这段代码是一个商品拍卖的智能合约。所有参与拍卖的人员对商品进行竞价,每次竞价时都会将相应的价格发送到智能合约中,合约会自动记录竞价人的报价,拍卖结束时,出价最高者获得拍卖品,同时出价最高者的钱会发送给受益人。其他人可以使用withDraw函数拿回自己的钱。代码详细内容见注释。

        pragma solididity ^0.4.21               // 声明使用的solidity版本contract SimpleAuction{                 // 声明一个SimplaAuction的合约类address public beneficiary;         // 拍卖受益人uint public auctionEnd;             // 拍卖截止日期address public highestBidder;       // 当前的最高出价人mapping(address => uint) bids;      // 所有竞拍者的出价,map结构address[] bidders;                  // 所有竞拍者数组// 需要记录的事件,event主要用来记录日志event HighestBidIncreased(address bidder, uint amount); // 出价最高的人发生变动event Pay2Beneficiary(address winner, uint amount);     // 竞拍成功者的钱发送给受益人/// constructor是构造函数/// _biddingTime 表示拍卖时长/// _beneficiary 表示拍卖受益人constructor(uint _biddingTime, address _beneficiary) public{beneficiary = _beneficiary;auctionEnd = now + _biddingTime;}/// 对拍卖进行竞价,如果之前出过价,就会把之前的价格与当前价格求和作为竞价function bid() public payable{...}/// 参与投标的人在拍卖结束后取回自己的钱function withdraw() public returns(bool){}/// 结束拍卖,将最高出价的钱发送给受益人function pay2Beneficiary() public returns(bools){}}   
  • 智能合约的构造函数名,最新版本使用constructor关键字,不推荐使用类名命名构造函数。构造函数只能有1个。构造函数仅仅在合约创建的时候调用一次。

  • bid()函数中,可以看到有一个 payable 关键字。如果一个函数添加了关键字payable,表明该函数接受转账,如果一个函数不写payable关键字,表明该函数不接受转账。

  • bid()函数, withdraw()函数,pay2Beneficiary()函数是成员函数,他们有public修饰,表示可供外部调用。

  • solidity中的map,其结构不支持遍历,这就意味着需要手动记录map中的元素。一般使用数组进行记录。上述代码中使用bidders记录参与竞拍的人。solidity中的数组元素既可以是定长数组,也可以是可变数组。

编写好智能合约之后,如何将该智能合约发布到区块链上呢?在以太坊中,发布一个智能合约,只需要将该合约的内容写入到一笔交易即可。具体过程如下:

  1. 利用一个外部帐户发起一个转账交易,这笔交易的收款地址为0x0,转账金额设置为0。
  2. 将智能合约代码编译为二进制字节码,并将这些二进制字节码写入交易的data域。
  3. 填写交易其他部分内容。
  4. 发布交易,交易执行完毕后会返回智能合约的地址。

通过上述步骤就可以创建一个智能合约,以后调用智能合约时就将交易的收款地址写为智能合约的地址即可。

调用智能合约

智能合约无法主动执行,因此智能合约要么是被外部帐户调用,要么被其他智能合约调用,外部账户调用智能合约和内部账户调用智能合约的方法有所不同,下文将分别予以说明。

外部账户调用智能合约

外部账户调用智能合约时,具体步骤如下:

  • 创建一笔交易,交易的收款地址为要调用的智能合约的地址。
  • 把要调用的函数名称和以及该函数需要的参数进行编码,随后填入data域中。
  • 如果调用的函数有关键字payable修饰,即该合约接收转账,那么该函数中用到的转账金额则放在交易的value域中。
  • 填写其他交易内容,发布交易。
  • 矿工收到该交易后,本地执行该交易,将执行结果打包到区块中,发布区块。

下图中的接收地址中填入了调用的智能合约地址,data域中填入了要调用的函数和参数的编码值,value为0。

https://img-blog.csdn.net/2018081321134912?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3Q0NjQxNDcwNDE1MmFiYw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

智能合约账户调用智能合约

智能合约之间的调用则不需要通过发布交易进行,而是直接使用代码进行交互,调用的方法一般分为2种:

  1. 创建被调用合约对象后直接调用相关成员函数。
  2. 使用address类型的call()函数。

创建对象后直接使用的示例代码如下。

    contract A{event LogCallFoo(string str);               // 定义一个事件function constructor(address addr) public{} // 构造函数 function foo(string str) return (uint){     emit LogCallFoo(str);                   // 写日志操作return 123;}}contract B{uint ua;// 在合约B中创建合约A的对象,然后调用A中的foo()函数,返回结果存在ua中function callAFooDirectly(address addr) public {A a = A(addr);ua = a.foo("call foo directly");}}

在上述示例代码中,合约B中构建了智能合约A的对象,然后调用了A中的foo函数。如果使用这种调用方式,如果在执行a.foo()的过程中出现了异常,那么callAFooDirectly()函数也会抛出异常。出现这种情况,会直接导致所在的交易回滚,而矿工不会退回执行中收取的交易费。

使用address类型的call()函数的示例代码如下。

    contract C{function callAFooByCall(address addr) public return (bool){bytes4 funcsig = bytes4(keccak256("foo(string)"));      // 将要调用的函数编码成为4字节 if(addr.call(funcsig, "call foo by func call"))         // address.call形式调用return true;return false;}}
  • 上述addr.call(funcsig,“call foo by func call”)中,funcsig表示被调用函数的签名,funcsig是一个4字节大小的参数。而"call foo by func call"则是被调用函数的参数。被调用函数的参数会被扩展成为32字节。
  • 如果函数执行成功,则会返回true,执行失败或者引发异常,则会返回false。
  • 上述示例中的addr变量,隶属于Address类型,指的是被调用的智能合约的地址。
  • 和第一种调用函数方法相比,使用address.call(),即使被调用函数失败,也不会引起交易回滚

实际上,还有另外一种智能合约调用方式,即使用delegatecall方法,而delegatecall则类似于我们的函数调用,delegatecall函数中使用的所有上下文参数,均来自于调用发起合约,而不是被调用的合约。
调用智能合约更多详细信息,参考solidity中文文档。

至此,调用智能合约的方法基本叙述完毕,而伴随着智能合约另外一些特征,本文也会予以介绍。

智能合约中的fallback()函数

fallback()是一个很特殊的函数。它是智能合约中的一个匿名函数,这个函数没有名称、没有参数,也没有返回值,只有访问类型和函数体。其形式如下:

funcion() public [payable]{...}

匿名函数只有如下两种情况下才会被调用:

  1. 向某个合约地址转账,data域为空时。
  2. 向某个合约地址转账,data域中填写函数在智能合约中不存在时

用一句话总结,就是data域中的数据被解析后找不到一个可以匹配的函数,就会调用fallback()函数。

fallback()函数仍然可以用payable修饰,添加了payable函数之后表明匿名函数接收转账,如果没有payable,表明该函数不接收转账。如果匿名函数没有payable的情况下转账金额不是0,此时执行fallback()函数就会抛出异常。

汽油费(gas fee)

智能合约的设计语言solidity是图灵完备语言,这就意味着智能合约中可以包括循环。随之而来的问题是,如果智能合约中出现死循环怎么办?而程序在执行之前无法判断是否会出现死循环。因此,智能合约中引入了汽油费。智能合约执行在EVM中,EVM对执行指令进行了标价,每执行一条指令,就需要消耗相应的汽油,不同的指令因为复杂程度不同,消耗的汽油量会有所不同。

回想一下以太坊中一笔交易的结构:

    type txdata struct{AccountNonce    uint;                   // 交易次数GasPrice        *bit.Int;               // 单位汽油价格GasLimit        uint64;                 // 本交易愿意支付的最大汽油量Recipient       *common.Address         // 接收账户地址Amount          *big.Int                // 转账金额Payload         []byte                  // data域}
  • 每个交易中都有一个gas limit字段,表明发起交易方最多支出的汽油量,另外,交易中的gas price字段表明交易发起方对每单位的汽油出的价格,gas price*gas limit就是这笔交易消耗的最大汽油费。
  • 如果执行中出现了死循环,执行所需要的gas fee就会超额,此时EVM就会强行停止智能合约的执行,并且回滚之前的所有操作,但之前执行消耗的汽油费不会退回给交易发起方,这样就能有效的防止死循环,同时避免以太坊中的节点收到Denial of Service攻击。

智能合约中的条件判断

以太坊中的交易进行执行,可以看作是一个原子操作,要么全部执行完毕,完成转账;如果执行抛出异常,则执行中的操作全部回滚。所以智能合约在执行时有如下条件判断的语句,在执行前会判断条件,说明如下:

  • 智能合约中不存在自定义的try-catch的结构。
  • 智能合约执行过程中遇到异常,除非特殊情况,否则本次的执行操作会全部回滚。
  • solidity中可以抛出错误的语句有:
    • assert(bool condition):如果条件不满足就会抛出错误,用于抛出内部错误,和c++中的assert相同,可以用于Debug。
    • require(bool condition):如果条件不满足,也抛出错误,用于检测外部输入条件是否合法。
    • revert():无条件抛出异常,终止运行并且回滚状态变动。

智能合约执行中可以调用的变量

    // 获取给定区块的哈希值,只能获取最近的256个区块,不包括当前区块。   block.blockhash(uint blockNumber) returns (bytes32)                                                  block.coinbase(address)                 // 挖出当前区块的矿工地址                 block.difficulty(uint)                  // 当前区块的难度block.gaslimit(uint)                    // 当前区块的gas限额block.number(uint)                      // 当前区块号block.timestamp(uint)                   // 当前区块以秒计数的时间戳// 如下是智能合约可以获得的调用信息msg.data        (bytes)                 // 完整的调用信息(calldata)mas.gas         ( uint)                 // 剩余的gasmas.sender      (address)               // 消息发送者(当前调用)msg.sig         (bytes4)                // calldata的前4字节(即函数标识符)msg.value       (uint)                  // 随消息发送的wei的数量now             (uint)                  // 目前区块的时间戳(和前面的block.timestamp相同)tx.gasprice     (uint)                  // 交易的gas价格tx.origin       (address)               // 交易发起者

需要说明的有如下两点:

  • 智能合约调用的信息,全部是变量,而不是函数调用,括号中的类型,是这些变量的返回类型。
  • msg.sender和tx.origin是有区别的,msg.sender表示调用当前合约的地址,不一定是交易的发起者。因为一笔交易中发起的合约A可以调用合约B,此时对于B来说,msg.sender是A,tx.origin是交易发起者。

智能合约中的地址类型

变量类型说明
address.balance成员变量,uint256类型返回uint256类型,返回address中以Wei计量的余额
address.transfer(uint256 amount)成员函数向address所在的地址发送amount数量的Wei,失败时抛出异常,发送2300gas矿工费,该矿工费不可调节。
address.send(uint256 amount)成员函数,return (bool)向address发送amount书来那个的Wei,失败时返回false,调用时发送2300的gas矿工费,该矿工费不可调节。
address.call(…)成员函数,return (bool)发出底层CALL,失败返回false,发送所有可用的gas进行调用,发送的gas不可调节。
address.callcode(…)成员函数,return (bool)发出底层CallCODE,失败时返回false,发送所有可用的gas,发送的gas不可调节。
address.delegatecall(…)成员函数,return (bool)调用底层DELEGATECALL,失败返回false,发送所有可用gas发送的gas不可调节

注意:所有智能合约都可以显式的转换称地址类型。transfer和send以及call都可以用来进行转账,区别在于发送的汽油费不同。

智能合约执行中的一些问题

  • 矿工执行某个调用智能合约的交易,执行过程中出错,是否需要发布到区块链上?

    • 答:需要发布到区块链上,虽然执行失败,但是需要扣掉gas fee,发布到区块链上,其他矿工执行失败时也相应的扣掉汽油费,只不过此时扣掉的汽油费不是转给自己,而是转给发布区块的矿工账户。
  • 先执行智能合约再发布区块,还是先发布区块再执行智能合约?

    • 答:先执行智能合约,再发布到区块。每一个新发布区块中最新的三个状态树、交易树、收据树的哈希值,都是执行完智能合约之后才能得到。挖到区块的矿工发布区块之后,其他矿工随之执行新区块中的交易,同步更新本地存储的状态树、交易树和收据树,以此维持数据同步。
  • 智能合约支持多线程吗?

    • 智能合约的solidity不支持多线程。以太坊是一个交易驱动的状态机,因此面对同一种输入,必须到达一个确定的状态。但是多线程的问题在于多核对内存访问顺序不一样,就会引起状态变化,这不利于维护区块链中状态的一致性。同时,其他可能造成不一致的操作,智能合约也不支持。最明显的例子就是以太坊中的智能合约没办法产生真正意义下的随机数。

http://chatgpt.dhexx.cn/article/RMDe5jaZ.shtml

相关文章

智能合约(一)————智能合约入门

1、智能合约的基本组成 1.1.程序版本 1.2. 合约声明 1.3.状态变量 1.4.合约方法 在这里constant相当于他声明这个局部变量不能更改,但是他并没有实际作用(实际就只是警示作用)2、地址adress - address.balance 账户余额 - adress.transfe…

EOS 智能合约源代码解读 (10)token合约“简介”

1. 记录用户的token,比如有哪些代币 class [[eosio::contract("eosio.token")]] token : public contract {public:using contract::contract;[[eosio::action]]void _create( const text_name& issuer, const asset& maximum_supply);[[eosi…

区块链智能合约介绍

作者:qinyutong、chengyueqiang 智能合约 (smart contract) 是一种由事件驱动的、具有状态的代码合约和算法合同 [11],随着以比特币为代表的区块链技术的蓬勃发展, 区块链技术已经开始逐步超越可编程货币时代而进入智能合约时代。智能合约作为区块链的核…

智能合约简介

区块链技术简史 区块链技术的第一次应用是在2008年,当时比特币首次亮相。抛弃传统金融机构,比特币引入了促进数字金融交易的新方法。然而,尽管在当时这是革命性的举措,但在这种状态下,区块链技术无法得到广泛应用。 区…

十分钟教你开发EOS智能合约

在CSDN、柏链道捷(PDJ Education)、HelloEOS、中关村区块链产业联盟主办的「EOS入门及最新技术解读」专场沙龙上,柏链道捷(PDJ Education)CTO、副总裁康烁,作了「如何在EOS上开发智能合约」的精彩演讲。演讲…

智能合约--如何实现可升级的智能合约

一. 什么是智能合约 智能合约通俗点说就是写在区块链上面的代码,代码里面编写着严谨完善的规则,一旦某个用户满足了合约里面的规则条件,就会触发里面的代码,执行某个方法。 二. 为什么要使智能合约达到可升级 智能合约的特点之一…

智能合约简单介绍

本学期学习了区块链的课程,作业是对于智能合约学习后的报告: 1 智能合约简单了解 1.1智能合约是什么 智能合约是由事件驱动的、具有状态的、部署于可共享的分布式数据库上的计算机程序,多用IF-THEN语句。狭义来说,智能合约是设计…

智能合约

智能合约(英语:Smart contract )是一种旨在以信息化方式传播、验证或执行合同的计算机协议。智能合约允许在没有第三方的情况下进行可信交易。这些交易可追踪且不可逆转。[1]智能合约概念于1994年由Nick Szabo首次提出。智能合同的目的是提供…

[EOS源码分析]6.EOS特殊智能合约eosio

这里说的eosio智能合约不是泛指eos的智能合约,它是一个特殊的具体的合约。它本事可大了,我们一起来看看它有哪些功能 负责智能合约部署 大家有注意到如下红色字体的log吗 $ cleos set contract hello.code ../eos-contract/hello -p hello.code Publish…

EOS智能合约开发(一)

一、 智能合约功能二、 通讯模式 2.1 运行机制2.2 内联通信2.3 延迟通信2.4 交易和动作2.5 交易确认2.6 动作处理程序和动作的apply上下文2.7 交易限制 三、文件结构 3.1 创建框架3.2 apply处理程序3.3 EOSIO_ABI 宏 四、多索引数据库API 4.1 EOSIO多索引API4.2 EOSIO多索引迭代…

EOS系列 - WASM智能合约 - 特性

构造函数 addressbook(name receiver, name code, datastream<const char*> ds):contract(receiver, code, ds) {}#单例表&#xff08;code和scope都用receiver的表&#xff09;也可在初始化列表中实例化 singleton_example( name receiver, name code, datastream<c…

EOS智能合约开发(十五)EOS 状态机架构解析

好久没有写文章了&#xff0c;最近公司事情比较忙。非常感谢上次杭州团队一位负责EOS状态机的同事精彩分享。今天查阅很多资料后&#xff0c;整理这篇文章&#xff0c;希望对大家有所帮助。 EOS状态机是什么&#xff1f; 简单讲就是数据库&#xff0c;是EOS记录智能合约执行结…

接口测试时,输入所有参数的参数值后,接口返回“参数错误:所有参数都不能为空”

问题原因&#xff1a; 参数名称输入有误&#xff0c;前台&#xff08;pwd&#xff09;和后台&#xff08;password&#xff09;的参数名称不一致 &#xff0c;保持一致后即可

matlab 错误使用 connector.internal.autostart.run输入参数的数目不足。

matlab打开后提示&#xff1a; 错误使用 connector.internal.autostart.run输入参数的数目不足 原因&#xff1a;自己写的一个函数和matlab自带的函数重名了 参考&#xff1a;警告: 在 matlabrc 中初始化 Java 首选项失败 – MATLAB中文论坛MATLAB中文论坛MATLAB 基础讨论板块…

Matlab 自带遗传算法函数 ga() 运行报错:“ fitnessfcn() 输入参数的数目不足。”

原始程序 %主程序 m [1 2]; R [1.5 1]; epsilon [2^R(1)-1 2^R(2) - 1]; A [-1 epsilon(1); 0 -1]; b [0 0]; Aeq [1 1]; Beq 1;%matlab自带函数包 [x_optimal, fval] ga(fitnessfcn, 2, A, b, Aeq, Beq, [0;0], [1;1], []); ---------------------------------…

matlab函数参数不足,调用函数显示输入参数不足

问题描述.png (29.7 KB, 下载次数: 1) 2015-1-27 09:34 上传 %Gauss-Newton算法实现如下 function[x,minf] = GN(f,x0,var,eps)formatlong; ifnargin == 3 %如果没有设置eps,则eps=1.0e-6eps = 1.0e-6; end m = 0; S =transpose(f)*f; %trnspose是转…

matlab 函数不定参数,matlab function定义一个函数,但一直出来说输入参数数目不足。我用的是2014版本,不知道数目原因啊?...

www.mh456.com防采集。 h 5;g 1; % g取源0有问题% 被积函数2113可以化5261简成f 4102(a,b,r)(r*h-r^16532*cos(a-b))/(h^2r^2g^2-2*h*r*cos(a-b))^1.5;% 求积分quadv((r)quadv((b)quadv((a)f(a,b,r), 0, 2*pi), 0, 2*pi), 0, h) 错误在于&#xff0c;调用函数f( x )缺一个…

matlab plot输入参数太多,输入参数太多问题

版本&#xff1a;R2016a 利用SCE-UA调用新安江模型进行率定时&#xff0c;显示参数太多&#xff0c;求大神指导。 clear; clc; spathcd P0(textread([spath,\matlabxingshanjiangyu.txt])); PP0(2,:);%降水量(mm) EI0(textread([spath,\matlabxingshanzhengfa.txt])); EIEI0(2,…

参数输入太多

参数输入太多 在向platEMO平台添加新的算法时&#xff0c;可能会出现参数输入太多的错误提示 可能是由于输入的参数内包含逗号&#xff0c;比如 a[1,2],b[3,4] 从而造成”输入参数太多“的错误提示。