在上一章(solidity教程)使用solidity与web3创作一个在线小游戏之一:proxy合约,call, delegatecall与callcode_lixiaodog的博客-CSDN博客
中,我们了解了可更新合约的基本原理,与solidity的三种函数调用方法,在本章中我们将完成一个基本的proxy,并写一个与之相配的可更新合约。在编写的过程中,我们还会接解到solidity的数组与mapping。
请看下面代码:
pragma solidity >0.4.24;
contract Proxy {
address public owner;
event Upgraded(address indexed implementation);
address internal _implementation;
uint storedData;
// function set(uint x) public {
// storedData = x;
// //x.call(bytes4(keccak256("set(uint x)")), 89);
// // p = Proxy(x);
// // p.set(89);
// //uint c = x.call(bytes4(keccak256("get()")));
// //console.log(c);
// //console.log(msg.value);
// }
// function get() public view returns (address) {
// return _implementation;
// }
constructor() public {
owner = msg.sender;
emit Upgraded(msg.sender);
}
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
function implementation() public view returns (address) {
return _implementation;
}
function upgradeTo(address impl) public onlyOwner {
require(impl != address(0), "Cannot upgrade to invalid address");
require(
impl != _implementation,
"Cannot upgrade to the same implementation"
);
_implementation = impl;
emit Upgraded(impl);
}
fallback() external payable {
address _impl = _implementation;
require(_impl != address(0), "implementation contract not set");
assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize())
let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)
let size := returndatasize()
returndatacopy(ptr, 0, size)
switch result
case 0 {
revert(ptr, size)
}
default {
return(ptr, size)
}
}
}
}
在这段代码里,实现了一个简单的可穿透的proxy基类,使用upgradeTo(address impl就可以改变它代理的目标合约。
完成了代理合约,我们就可以实现一个真实要执行的合约,看下图:
这个合约才是我们真正要执行的 合约,当成功代理之后,所有的函数调用将由这个合约来真正执行。在这个合约中,我们看到以前并未深入讲解过的数组与MAPPING,这两个类型是solidity中的复杂数据类型。数组可以分为定长数组和变长数组,那它们的实际存储有什么不同呢?
我们使用下面代码来检测,这里我将使用SOLIDITY的IDE工具remix来部署与测试代码:
contract D {
uint256[] public uArr;
uint256[3] public uArr1 = [1,2,3];
mapping(uint=>string) public i2s; //position is 0
function setArr(uint key) {
uArr.push(100);
uArr.push(200);
uArr[0] = 200;
}
function setInt(uint key) {
i2s[key] = "zzz";
}
}
在这段代码中,ARR是存在什么地方呢?这就涉及到了SOLIDITY的状态变量的存储模型。在使用remix编译并部署了合约之后,我们会看到如下界面:
在setArr中传入参数1,点击setArr, 然后在相应的输出出点击DEBUG
进入如下界面:
我们查看其中的storage发现第每个字段的KEY和VALUE在定长数组中是逐步增长的。
而对变长数组来讲,它的KEY看起来是突然变化,并没有按顺序增长,但实际上solidity在内部使用了一个算法来计算变长数组的存储位置,然后再根据数组的KEY来增长KEY值,而我们想像的插槽位置0存储的是什么呢?这里存储的是数组的长度。所以一定要注意变长数组与定长数组的存储区别。
大小固定的变量(除了映射
,变长数组
以外的所有类型)在存储(storage)中是依次连续从位置0
开始排列的。如果多个变量占用的大小少于32字节,会尽可能的打包到单个storage
槽位里,具体规则如下:
结构体
和数组
总是使用一个全新的槽位,并占用整个槽(但在结构体内或数组内的每个项仍遵从上述规则)
在这个规则中,我们可以知道,如果优化SOLIDITY的存储,即尽量的让变量可以组合成32个字节这样的数据,这样才能消耗更少的GAS。
对于MAPPING,在他的SLOT里是存储的什么呢?实际上在他的SLOT里 是空的。他的实际的所有VALUE者存储在另一个一一对应的KEY里。这个KEY的计算规则如下:keccak256(key.slot)。
在本章中,我们实践了PROXY合约,认识了SOLIDITY的状态变量的存储模型。还实现了一个实际 的Gamemanage合约。在下一章使用solidity与web3创作一个在线小游戏之三:(在VUE中使用WEB3,并使用Truffle包装对象与智能合约交互)_lixiaodog的博客-CSDN博客中,我们将使用vue与WEB3来做一个简单的与合约交互的管理平台,敬请期待
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)