使用solidity与web3创作一个在线小游戏之一:(proxy合约,call, delegatecall与callcode)

使用solidity与web3创作一个在线小游戏之一:(proxy合约,call, delegatecall与callcode),第1张

在我们的上个系列文章跟我一起阅读并修复某知名DEX交易所源码的最后,我们提到了proxy合约与admin合约,在这个新系列中,我将和大家一起使用solidity完成一个小游戏,并使用web3做一个客户的游戏客户端,并与之完成交互。现在让我们开始吧!

首先我们考虑到,一个游戏具有相当复杂的逻辑,而如果使用solidity编写,一旦合约部署到链上,那将无法更改,此时如果游戏要更新怎么办?那有没有什么办法可以让我们的合约可以更新呢?这样不就可以解决这个问题了吗?答案是有的,就是使用PROXCY合约。下面让我们看一下proxcy合约的原型。

contract Proxy {

  /**

  * @dev Tells the address of the implementation where every call will be delegated.

  * @return address of the implementation to which it will be delegated

  */

  function implementation() public view virtual returns (address){}

  /**

  * @dev Fallback function allowing to perform a delegatecall to the given implementation.

  * This function will return whatever the implementation call returns

  */

  fallback () payable external {

    address _impl = implementation();

    require(_impl != address(0));

    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) }

    }

  }

}

在这里,利用了SOLIDITY的一个特性,当调用目标没有要调用的函数时,就会调用合约里的fallback函数,这个fallback函数,必须是外部可见的。由此可以实现由Proxy合约代理函数调用,而实际执行的却是另外 一个合约 。这样更新的时候,把实际更新掉,而与USER实际交互的PROXY却不需要更新。

下面让我们看下主要函数:

 在上一个系列文章中我们已经接触过assembly了。我们看一下例子:

contract C {
  function f(uint x) returns (uint y) {
    y = 1;
    for (uint i = 0; i < x; i++)
      y = 2 * y;
  }
}

它将生成以下汇编内容

{
  mstore(0x40, 0x60) // store the "free memory pointer"
  // function dispatcher
  switch div(calldataload(0), exp(2, 226))
  case 0xb3de648b {
    let (r) = f(calldataload(4))
    let ret := $allocate(0x20)
    mstore(ret, r)
    return(ret, 0x20)
  }
  default { revert(0, 0) }
  // memory allocator
  function $allocate(size) -> pos {
    pos := mload(0x40)
    mstore(0x40, add(pos, size))
  }
  // the contract function
  function f(x) -> y {
    y := 1
    for { let i := 0 } lt(i, x) { i := add(i, 1) } {
      y := mul(2, y)
    }
  }
}

汇编有下面四个阶段:

解析脱汇编(移除switch,for和函数)生成指令流生成字节码

 在这里我们不需要对内联汇编了解很深,通过上面两段代码,我们对它有一个了解就可以了。现在回到我们的代码:

let result := delegatecall(gas(), _impl, ptr, calldatasize(), 0, 0)

proxy合约将通过这个关键的调用达到穿透的效果。在solidity中共有3种调用形式,看如下代码:

pragma solidity ^0.4.0; 
contract A {
    address public temp1;
    uint256 public temp2;
    function three_call(address addr) public {
            addr.call(bytes4(keccak256("test()")));                 // 情况1
             addr.delegatecall(bytes4(keccak256("test()")));       // 情况2
             addr.callcode(bytes4(keccak256("test()")));           // 情况3   
    }
} 

contract B {
    address public temp1;
    uint256 public temp2;    
    function test() public  {
            temp1 = msg.sender;        temp2 = 100;    
    }
}

这段演示了call, delegatecall与callcode的具体不同。实际上这3个调用,主要区别是2方面,一个是调用上下文sender的区别,一个是执行环境的区别,我们在合约中的大部分调实际上执行的是call调用。如果某个sender U呼叫A,A又call了B,此时sender是A,环境是A,同样假设如果 是A又delegatecall了B呢?此时sender是U,而不是A,执行环境却还是A,同样假设A callcodeB呢?此时sender又变成了A,而执行环境却变成了B。

通过PROXY合约的学习,我们了解了什么是穿透及合约更新的原理,在一下章使用solidity与web3创作一个在线小游戏之二:(proxy合约,solidity中的数组与mapping,状态变量的存储模型)_lixiaodog的博客-CSDN博客中,我们将使用这个原理来为我们的GameManage合约编写一个proxy合约,敬请期待。

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

原文地址: https://outofmemory.cn/zaji/925340.html

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

发表评论

登录后才能评论

评论列表(0条)

保存