聊一聊智能合约

本篇文章我们简单介绍了智能合约的一些基础概念,并针对没有编程基础的同学介绍了编程中常见的概念。和大家一起分析了个简易版的智能合约帮助大家了解智能合约的整体结构。最后结合智能合约比较热门的应用NFT了解了接口的概念,再次讨论了什么是上链。

智能合约到底是什么

智能合约概念

从抽象概念上讲,智能合约是一种可以自动化执行的规则,现实生活中的合约,合约制定完成后需要有专门的执行角色,而智能合约将这个步骤自动化,只有满足智能合约中制定的条件就会被执行。

从具体概念上讲,我们之前有一篇文章讲以太坊的数据结构中提到每个账户都有状态树,而智能合约的目的就是通过一系列逻辑执行触发合约账户的状态变化以及通过交易改变外部账户的状态,关于合约账号和外部账户可见"以太坊账户"一文。

编程语言的一些基础概念

变量(广义包括常量)

编程语言中,变量起着重要作用,用来存储内存数据。变量内存储的数据有各种各样的类型,有的是简单的整型,字符串,有的是较为复杂的集合类型,比如列表,映射表或上自定义的类型。变量还有1个很重要的概念就是作用域。

作用域

作用域代表变量的生命周期,是函数级别-函数执行完就销毁,还是对象级别,依附对象销毁就会销毁,或又是全局级别,只有程序在运行就一直都在。在智能合约中合约中声明的变量都是存储在状态树中,所以只要合约账户存在,这个变量就存在。这一点认识非常重要,后面在讲NFT的时候会讲1个误区。

函数

函数代表着逻辑的执行,函数的输入有几种 1。可以访问的内存 磁盘 网络中的输入 2.调用函数者传入的参数,函数的输出也是有2种 1.可以访问的内存 磁盘 网络的输出 2.函数返回值 。

有一种纯函数的概念 即 输入只包含了调用函数者传入的参数,输出只包含了返回值。

在智能合约中,非依赖库所有能够让外部账户或者合约账户调用的函数都不应该是纯函数,否则就是对gas的浪费。

智能合约的结构

智能合约结构示意

640.png

如上图,智能合约由外部账户发起调用,合约直接也可以相互调用,每个合约内部执行函数转移状态。

分析个例子

例子来源:

https://learnblockchain.cn/docs/solidity/solidity-by-example.html#id1

分析智能合约分为2块

1.合约变量,即状态每个状态代表了什么含义。

2.函数 即合约中的函数是如何运转的,又是如何影响状态变化。

合约变量

address public chairperson;
 // 这声明了一个状态变量,为每个可能的地址存储一个 `Voter`。 
mapping(address => Voter) public voters;
// 一个 `Proposal` 结构类型的动态数组Proposal[] public proposals;

这3个变量就是这个智能合约的状态

Address

mapping(address => Voter)

Proposal[]

表示了变量的类型 Address代表这是1个地址 mapping(address => Votor)代表是个映射表 其中key是地址 value是Voter Voter是个自定义的类型(就是一组类型的聚合),Proposal[] 代表是个数组 数组中每个元素都是Proposal Proposal也是个自定义类型

struct Voter {        
    uint weight; // 计票的权重       
    bool voted;  // 若为真,代表该人已投票        
    address delegate; // 被委托人        
    uint vote;   // 投票提案的索引    
}
    // 提案的类型    
struct Proposal {        
    bytes32 name;   // 简称(最长32个字节)        
    uint voteCount; // 得票数
}

合约函数

function开头的代表是一系列函数就是和前面说到的3个状态交互

6401.png

如上图所示,智能合约中对外可调用的函数围绕着如何修改 或 读取状态而来,如同文章开始所讲到的,智能合约执行的过程就是以太坊中状态树变化的过程。

智能合约的特点

gas费用

智能合约相比于其他编程非常要注意的1点就是运行逻辑需要收取gas费用,这使得合约编写过程要额外注重代码的高效性,尽可能地减少冗余代码。

gas费用存在的原因以及gas费用为什么高昂,之前不可能三角一文中有讲,简单来说gas费用存在是因为计算需要耗费资源,存在gas费用可以避免恶意消耗资源的情况发生。gas费用高昂主要是因为竞争激烈。

gas费用计算可以看这里

https://ethereum.org/zh/developers/docs/evm/opcodes,这里是一些操作码,我们写的代码最终都会编译成字节码指令运行在EVM上

围绕交易的语言设计

payable 关键字

这是一个函数的修饰符,只有被payable修饰过的函数才可以在调用时附带ETH。

fallback函数

调用到智能合约没有的函数时会走到fallback函数, 如果没有写fallback会抛出异常,如果是涉及到附加ETH,fallback必须被payable修饰,否则也会抛出异常

receive函数

对合约单纯转账会进入这个函数,前提是有这个函数,如果没有会进入被payable修饰的fallback函数,否则会抛出异常。

可以看到合约语言无不透露着对交易属性,这在一般的编程语言中是不会看到的。

常见陷阱

合约一旦发布,即使存在漏洞就不可更改,所以对于逻辑要格外严谨。我们来看看1个常见漏洞

重入攻击
//被调用的合约
contract A {        
    uint money;    /// 提取你的分成。
    function withdraw() public {
          if (payable(msg.sender).send(money))
              money = 0; 
         }
}
//调用的合约
contract B {
        function hack() public {
            A().withdraw();
       }
        fallback external payable {
        A().withdraw();
       }
}

上面的代码中A给B转账会走到B的fallback函数,而B的fallback函数又调用A的转账函数,这样会导致A向B不停转账。

改进办法就是先修改状态再转账

//被调用的合约
contract A {
        uint money;    /// 提取你的分成。
        function withdraw() public {
            uint tmp_money = money; 
            money = 0;
            payable(msg.sender).send(moeny);
        }
}

NFT

NFT是最近比较热门的话题,很多人认为NFT代表了数字化的确权,也有人认为目前就是一张jpg图片,我们这里不讨论他的经济价值,讨论NFT的技术实现。在每一组NFT背后都是一份智能合约。这些智能合约都遵守着相同的规范。

NFT协议

具体见:https://docs.openzeppelin.com/contracts/3.x/api/token/erc721#ERC721

constructor(name_, symbol_)
balanceOf(owner)
ownerOf(tokenId)
name()
symbol()
tokenURI(tokenId)
baseURI()
tokenOfOwnerByIndex(owner, index)
totalSupply()
tokenByIndex(index)
approve(to, tokenId)
getApproved(tokenId)
setApprovalForAll(operator, approved)
isApprovedForAll(owner, operator)
transferFrom(from, to, tokenId)
safeTransferFrom(from, to, tokenId)
safeTransferFrom(from, to, tokenId, _data)
_safeTransfer(from, to, tokenId, _data)
_exists(tokenId)
_isApprovedOrOwner(spender, tokenId)
_safeMint(to, tokenId)
_safeMint(to, tokenId, _data)
_mint(to, tokenId)
_burn(tokenId)
_transfer(from, to, tokenId)
_setTokenURI(tokenId, _tokenURI)
_setBaseURI(baseURI_)
_approve(to, tokenId)
_beforeTokenTransfer(from, to, tokenId)

上面的函数就是NFT智能合约需要遵守的规范- ERC721。

在编程语言中有个概念-接口。之前讲过函数的概念,而接口就是一组函数声明,要实现类似的东西大家都要有个规范,于是乎就有了接口。接口不负责具体实现,只是告诉你要实现什么能力,才能够满足规范。

我们从接口中挑1个大家有过讨论的函数看看。

_setTokenURI(tokenId, _tokenURI)

这个函数目的是给这组NFT某个编号 设置一个url,这个url我们可以简单地理解为是一张图片(先简单理解)。这个接口的出现导致了,如果可以成功调用该接口,那么你高价购买到的NFT将有可能变成另外1张没有任何艺术价值的图片。

有的说法是这个url没有上链,如果上链了就不会有这个问题。所以我们接下来讨论下什么是上链。

当我们讨论上链时,什么是上链

很多人讨论链上的数据不可被更改,那到底什么是上链呢?在之前文章以太坊数据结构中曾经提到以太坊当中的状态树,只要存在这棵状态树里面的数据都是上链了的。而智能合约中每个合约状态都是存在状态树中的,所以只要是合约中声明的变量都可以称为上链 。另外1个问题,链上数据是否不可更改呢?准确回答说按照规则就可以更改,不按照规则就不可以更改。什么是按照规则,按照规则就是如果智能合约中有对应修改这个状态的函数,满足调用函数的条件,就可以改掉它!

回到NFT url的问题,这个url肯定是存到状态树上了,毋容置疑也是上链了的,但就因为暴露了这个接口所以才导致可以被更改。

这里另外提1个NFT url篡改的风险在于同1个url内映射的文件也可能被更改,这里就需要用到不可变更的存储,如ipfs等,后面我们专门有一篇内容来分析分布式存储。

结尾

本篇文章我们简单介绍了智能合约的一些基础概念,并针对没有编程基础的同学介绍了编程中常见的概念。说到学习代码唯有2招最为有效,1.多读代码 2.多写代码 所以我们和大家一起分析了个简易版的智能合约帮助大家了解智能合约的整体结构,并分析了智能合约一些针对交易设计的特点。最后结合智能合约比较热门的应用NFT了解了接口的概念,再次讨论了什么是上链。文中我们多次涉及到之前写的以太坊数据结构一文,可见底层知识的重要性。

更多web3相关文章 可关注公众号 "web3探索者"

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
web3探索者
web3探索者
0x3167...f450
元宇宙新著民致力研究web3 会定期分享web3技术 公众号:web3探索者