使用solidity与web3创作一个在线小游戏之二:(proxy合约,solidity中的数组与mapping,状态变量的存储模型)

使用solidity与web3创作一个在线小游戏之二:(proxy合约,solidity中的数组与mapping,状态变量的存储模型),第1张

在上一章(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槽位里,具体规则如下:

在storage槽中第一项是按低位对齐存储(lower-order aligned)基本类型存储时仅占用其实际需要的字节。如果基本类型不能放入某个槽位余下的空间,它将被放入下一个槽位。结构体数组总是使用一个全新的槽位,并占用整个槽(但在结构体内或数组内的每个项仍遵从上述规则)

在这个规则中,我们可以知道,如果优化SOLIDITY的存储,即尽量的让变量可以组合成32个字节这样的数据,这样才能消耗更少的GAS。

对于MAPPING,在他的SLOT里是存储的什么呢?实际上在他的SLOT里 是空的。他的实际的所有VALUE者存储在另一个一一对应的KEY里。这个KEY的计算规则如下:keccak256(key.slot)。

在本章中,我们实践了PROXY合约,认识了SOLIDITY的状态变量的存储模型。还实现了一个实际 的Gamemanage合约。在下一章使用solidity与web3创作一个在线小游戏之三:(在VUE中使用WEB3,并使用Truffle包装对象与智能合约交互)_lixiaodog的博客-CSDN博客中,我们将使用vue与WEB3来做一个简单的与合约交互的管理平台,敬请期待 

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/942546.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-18
下一篇 2022-05-18

发表评论

登录后才能评论

评论列表(0条)

保存