创建可与 MetaMask 一起使用的可升级代理合约

create upgradable proxy contract that works with MetaMask

我正在创建一个连接到现有 ERC-20 合约的代理合约。 该代理合约应该能够连接到元掩码并显示代币余额。

在带有代理地址的元掩码中添加令牌时一切正常,它正确显示符号和十进制数但不平衡。而是显示为零。

代理合约代码:

contract Proxy  {

    address private _implementation; 
    event Upgraded(address indexed implementation); 

    function implementation() public view returns (address) {
    return _implementation;
    }

    function upgradeTo(address impl) public  {
    _implementation = impl;
    emit Upgraded(impl);
    }


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

函数 balanceOf 当我在具有 ERC-20 合约地址的元掩码中添加令牌时工作正常。但代理合约显示为零

function balanceOf(address tokenOwner) public view  returns (uint256) { 
    return balances[tokenOwner];
}

我的努力

为了测试我写了这个函数:

function test(address theAddress) public view  returns (address) { 
    return theAddress ;
}

当我调用参数时 '0xC357c241b98B15B3A08aeC3AcD49fBC0cbD74fcE'ERC-20 合同 returns 相同地址但在代理 returns 这个值:

0xc357c241b98b19150f7f8f1d47ad1cd500000000

我做的另一个测试是这个功能:

function test2(string memory theString) public view  returns (string memory) { 
    return theString ;
}

此功能在代理和 ERC-20 合约上都能正常工作!!

谢谢大家。

编辑 1

我的测试 web3.js

var interval ;
document.addEventListener('DOMContentLoaded', function() {
      interval =  setInterval(run , 1000);
}, false);


function run(){
    web3 = new Web3(web3.currentProvider); 
    console.log("call");
    if(web3.eth.accounts[0] === undefined)
        return;


    clearInterval(interval);
    console.log(web3.eth.accounts[0]); 

   web3.eth.defaultAccount = web3.eth.accounts[0];
  var CoursetroContract = web3.eth.contract( JSON.parse(`[

{
    "constant": true,
    "inputs": [
        {
            "name": "theAddress",
            "type": "address"
        }
    ],
    "name": "test",
    "outputs": [
        {
            "name": "",
            "type": "address"
        }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
},
{
    "constant": true,
    "inputs": [
        {
            "name": "theString",
            "type": "string"
        }
    ],
    "name": "test2",
    "outputs": [
        {
            "name": "",
            "type": "string"
        }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function"
  }
]`));

 var contract = CoursetroContract.at('0xd3744cac3a2f796c16b45e5be582c1c5f3039482'); //proxy

//var contract = CoursetroContract.at('0xd025c8835b2a4bd2f9eeb1d682db224f7b301868'); //erc20

contract.test(0xC357c241b98B15B3A08aeC3AcD49fBC0cbD74fcE,
            function(err,result){
                console.log("err" ,err);
                console.log("result" , result);
            }
        );       

编辑 2

此合约地址已在 Ropsten Testnet

中可用

代理合约的工作方式略有不同。

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

您的代理合约中的 DELEGATECALL 通过 _impl 中指定的地址调用合约。因此,它在代理合同的环境中运行 _impl 代码(在您的例子中是 ERC20)。因此,代理的存储被修改,而不是 ERC20 合约存储。 Link如何delegatecall works.

所以我的建议是查看您如何初始化 ERC20 合约并设置其余额。

你必须这样做

erc20Contract = await erc20Contract.at(proxy.address)

erc20Contract.initialize()

第一行是erc20Contract在代理合约地址的接口。 第二行将在代理合约的地址和存储处重做构造函数的工作。