在我们的上个系列文章跟我一起阅读并修复某知名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合约,敬请期待。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)