智能合约的升级
做软件开发的都知道,没有完美的代码,在当前设计的时候由于考虑不完备或者后续需要增加一些新功能,需要对软件进行迭代升级。在以太坊的智能合约中也存在这种情况,由于功能的完善可能会"升级"智能合约。大家都知道,部署到链上的合约代码是不可修改的,怎么实现合约代码的"升级"呢?
其实智能合约的"升级"并不是真正意义上的在原合约上的代码调整,在以太坊中上链的数据是无法修改的,而是通过合约架构去实现类似合约"升级"的功能,一般其架构如下图:
从上图可以看到,DAPP首先调用代理合约,代理合约内部存储部署业务合约的部署地址,通过该地址调用业务合约进行逻辑处理;在进行逻辑处理的过程中需要用到相关数据,业务合约存储部署数据合约的地址并请求相关数据。将业务和数据进行分离,这样就算升级业务合约代码之前的业务核心数据并不会擦除。如今后扩展新的业务,增加新的数据类型只要重新部署一份数据合约4即可。通过这种方式,可以有效的将业务和数据进行分析,两者互不干扰并且具有很好的扩展性;针对业务合约的升级只需要重新部署一份业务合约并在代理合约对调用业务合约的地址进行重新赋值即可。
下面我将列举一个示例来说明上述模型在实际中的应用。
比如小强就读某小学二年级,纳入教学评估的有数学和语文两门学科,现在要在教学主链上部署一个统计小强每次教学考试语文。数学以及两科总成绩的合约,要求成绩统计数据和成绩统计逻辑要分离。则其合约架构就类似上图结构,业务合约就是各科成绩统计,数据合约1保存语文成绩统计数据,数据合约2保存数学成绩统计数据,数据合约3为总成绩统计数据。下面就来实现相关合约代码。
数据合约:
pragma solidity ^0.4.17; //数学成绩保存 合约代码 contract MathGradeData { address public manager; //允许对数据进行修改的合约调用地址 address public deploy; //部署本次合约的地址 mapping (address => uint16[]) public GradeOf; //成绩存储 function MathGradeData () public{ deploy = msg.sender; //构造函数,赋值合约部署方地址 } function setDrage(address _address,uint16 _vaule) public onlyManagerCanCall{ require(_vaule <= 200); require(_vaule >= 0); GradeOf[_address].push(_vaule); //对成绩进行赋值操作(只有允许的地址才能调用) } function getDrage(address _address,uint256 _off) public returns (uint16) { require(_off < GradeOf[_address].length); return GradeOf[_address][_off]; //查询成绩 } function setManager(address _address) public onlyDeloyCanCall { manager = _address; //设置对成绩进行赋值操作的地址 } function getManager() public view returns (address){ return manager; //获取对成绩进行赋值操作的地址 } modifier onlyManagerCanCall(){ //函数修改器 require(msg.sender == manager); _; } modifier onlyDeloyCanCall(){ //函数修改器 require(msg.sender == deploy); _; } } //语文成绩保存 合约代码 contract ChineseGradeData { ...... //代码同上 } //综合成绩保存 合约代码 contract TotalGradeData { ...... //代码同上 }
上述源码主要介绍数据合约源码,接下来介绍一下业务合约源码,即数据统计合约:
pragma solidity ^0.4.17; contract BusinessContract { MathGradeData private Math; //部署数学数据的合约代码 ChineseGradeData private Chinese; //部署语文数据的合约代码 TotalGradeData private Total; //部署综合成绩数据的合约代码 address public deploy; //合约部署方的地址 address public manager; //允许对关键数据进行写操作的地址 function BusinessContract (address _math,address _chinses,address _total) public{ deploy = msg.sender; //构造函数 Math = MathGradeData (_math); Chinese = ChineseGradeData (_chinses); Total = TotalGradeData (_total); } function setDataContractAddress(address _math,address _chinses,address _total) public onlyDeloyCanCall { Math = MathGradeData (_math); //合约部署方运行更改数据合约地址 Chinese = ChineseGradeData (_chinses); Total = TotalGradeData (_total); } function writeGrade(address _student,uint16 _math,uint16 _chinese) public onlyManagerCanCall { Math.setData(_student,_math); //写入数据 Chinese.setData(_student,_chinese); uint16 tmp = _math+_chinese; Total.setData(_student,tmp); } function readGrade(address _student,uint _off) public returns (uint16 _math,uint16 _chinses,uint16 _total) { _math = Math.getData(_student,_off); //读取数据 _chinses = Chinese.getData(_student,_off); _total = Total.getData(_student,_off); } function setManager(address _address) public onlyDeloyCanCall { manager = _address; //管理地址进行赋值 } function getManager() public view returns (address){ return manager; //读取管理地址 } modifier onlyDeloyCanCall(){ //函数修改器 require(msg.sender == deploy); _; } modifier onlyManagerCanCall(){ //函数修改器 require(msg.sender == manager); _; } }
上述是业务合约的代码逻辑,接下来介绍一下代码合约的代码:
pragma solidity ^0.4.17; //代理合约 contract Agent { BusinessContract private Business; //业务合约 address public deploy; //代理合约部署方地址 function Agent(address _business) public{ //构造函数 deploy = msg.sender; //赋值部署代理合约地址 Business = BusinessContract(_business); //创建BusinessContract合约实例 } function setBusinessContractAddress(address _business) public onlyDeloyCanCall { Business = BusinessContract(_business); } function Read(address _student,uint _off) public onlyDeloyCanCall returns (uint16 _math,uint16 _chinses,uint16 _total) { return Business.readGrade(_student,_off); //读取相关数据 } function Write(address _student,uint16 _math,uint16 _chinses) public onlyDeloyCanCall { Business.writeGrade(_student,_math,_chinses); //写入相关数据 } modifier onlyDeloyCanCall(){ require(msg.sender == deploy); //函数修改器 _; } }
在上述数据合约,业务合约,代理合约之间的调用通过manager来授权,只有通过授权的地址才能调用合约进行写数据操作,防止其他无关账户随意调用。而manager是可以变动的,只有部署该合约的地址才能修改。针对上述3种合约,其对应关系如下图:
应重点关注部署地址和管理地址的前后关系,就明白代码中deploy和manager两个地址的作用。
后续学校不想以分数为具体的批判标准,以优秀,良好,及格,不及格来表示学生的成绩,分别以数字0,1,2,3来表示,具体定义如下:
>= 90 :优秀
> 90 >= 80:良好
> 80 >= 60:及格
> 60:不及格
针对上述逻辑的改动,我们只需要改动逻辑合约代码即可,改动如下:
pragma solidity ^0.4.17; contract BusinessContract { MathGradeData private Math; //部署数学数据的合约代码 ChineseGradeData private Chinese; //部署语文数据的合约代码 TotalGradeData private Total; //部署综合成绩数据的合约代码 address public deploy; //合约部署方的地址 address public manager; //允许对关键数据进行写操作的地址 function BusinessContract (address _math,address _chinses,address _total) public{ deploy = msg.sender; //构造函数 Math = MathGradeData (_math); Chinese = ChineseGradeData (_chinses); Total = TotalGradeData (_total); } function setDataContractAddress(address _math,address _chinses,address _total) public onlyDeloyCanCall { Math = MathGradeData (_math); //合约部署方运行更改数据合约地址 Chinese = ChineseGradeData (_chinses); Total = TotalGradeData (_total); } function writeGrade(address _student,uint16 _math,uint16 _chinese) public onlyManagerCanCall { uint16 writeData = 0; if(_math >= 90){ writeData = 0; }else if((_math < 90)&&(_math >= 80)){ writeData = 1; }else if((_math < 80)&&(_math >= 60)){ writeData = 2; }else{ writeData = 3; } Math.setData(_student,writeData ); //写入数据 if(_chinese>= 90){ writeData = 0; }else if((_chinese< 90)&&(_chinese>= 80)){ writeData = 1; }else if((_chinese< 80)&&(_chinese>= 60)){ writeData = 2; }else{ writeData = 3; } Chinese.setData(_student,writeData ); uint16 tmp = (_math+_chinese)/2; if(tmp >= 90){ writeData = 0; }else if((tmp < 90)&&(tmp >= 80)){ writeData = 1; }else if((tmp < 80)&&(tmp >= 60)){ writeData = 2; }else{ writeData = 3; } Total.setData(_student,writeData ); } function readGrade(address _student,uint _off) public returns (uint16 _math,uint16 _chinses,uint16 _total) { _math = Math.getData(_student,_off); //读取数据 _chinses = Chinese.getData(_student,_off); _total = Total.getData(_student,_off); } function setManager(address _address) public onlyDeloyCanCall { manager = _address; //管理地址进行赋值 } function getManager() public view returns (address){ return manager; //读取管理地址 } modifier onlyDeloyCanCall(){ //函数修改器 require(msg.sender == deploy); _; } modifier onlyManagerCanCall(){ //函数修改器 require(msg.sender == manager); _; } }
将编译好的合约重新部署,代码合约和数据合约无需改动即可实现相关功能,注意需要调整合约的manager。
以上就是合约升级的相关逻辑。在上述的举例中不太合理,只是想通过一个例子来说明合约升级的原理和过程,大家不必深究!
- 如何编写皇冠体育足球竞猜网站开发一个可升级的智能合约
- 如何编写一个可升级的智能合约
- 经典:浅谈以太坊智能合约的设计模式与升级方法
- 智能合约升级
- 如何编写一个可升级的智能合约
- Solidity智能合约升级解决方案
- 以太坊:调用已部署的智能合约
- 【区块链学习】以Linux为例,通过控制台部署和运行智能合约
- 如何编写智能合约之一:智能合约编写、编译以及部署
- 在以太坊私有链上部署智能合约
- 使用以太坊智能合约建立代币教程
- 智能合约图形化部署和运行原理
- 理解智能合约
- 以太坊智能合约学习1-My Token
- 一步一步学区块链(5)智能合约
- 在 hyperledger fabric 环境对Validating Peers布署 chaincode (智能合约)
- ETH 基础篇 JAVA Web3j 智能合约
- 区块链智能合约solidity入门
- 以太坊私链上开发一个ICO智能合约
- NEO智能合约开发(一)不可能完成的任务