I am currently writing an upgradable contract using the unstructured data proxy model and an ERC20 token implementation (similar to USDC).

This means the traditional approach of sending a transaction to a proxy and it hitting the fallback function (unless you are the admin) and delegating the call to the implementation while storing the state in the proxy.

Currently, I am writing a test suite that deploys both contracts and sets the implementation's initialise function via the proxy.

I am running into an error that no matter what call I give the proxy, whether it be asking for the symbol via "symbol()" or asking for the minter role, it always reverts (but shouldn't):

Error: Returned error: VM Exception while processing transaction: revert

If I use the admin key, it correctly fails with:

Error: Returned error: VM Exception while processing transaction: revert Cannot call fallback function from the proxy admin

However, it should not revert when calling functions defined in the implementation. My code for this test is as follows:

const { accounts, contract, web3 } = require('@openzeppelin/test-environment');
const [ admin, user, recipient ] = accounts;
// Loads the built artifact from build/contracts/ERC20.json
const ERC20 = contract.fromArtifact('ERC20');
const Proxy = contract.fromArtifact('Proxy');
// test framework
const { expect } = require('chai');
//erc20 generic tests
const shouldBehaveLikeErc20 = require("./behaviors/ERC20.behavior.js");
const shouldBehaveLikeErc20Detailed = require("./behaviors/ERC20Detailed.behavior.js");

describe('ERC20', function () {

    const name = 'ERC20';
    const symbol = 'ERC20';
    const decimals = 18;
    const totalSupply = web3.utils.toBN("1e28");

    beforeEach(async function() {
        this.ERC20 = await ERC20.new();
        this.ERC20V2 = await ERC20.new();
        let data = web3.eth.abi.encodeFunctionSignature('initialize()');
        this.Proxy = await Proxy.new(this.ERC20.address, admin, data, { from: admin });

    it("should behave like ERC20", function() {
        //need ERC20 ABI with Proxy address as Proxy delegates
        this.token = this.ERC20;
        this.token.address = this.Proxy.address;
        shouldBehaveLikeErc20(accounts, totalSupply);
        shouldBehaveLikeErc20Detailed(name, symbol, decimals)

    it('the deployer is the minter', async function () {
        let abi = {
                "constant": true,
                "inputs": [
                        "internalType": "address",
                        "name": "account",
                        "type": "address"
                "name": "isMinter",
                "outputs": [
                        "internalType": "bool",
                        "name": "",
                        "type": "bool"
                "payable": false,
                "stateMutability": "view",
                "type": "function"
        let functionData = web3.eth.abi.encodeFunctionCall(abi, [ admin ]);
        //always reverts here, but it shouldn't 
        let result = await web3.eth.call({
            from: user,
            to: this.Proxy.address,
        expect(web3.eth.abi.decodeParameter('bool', result)).to.equal(true);

    it('upgrades the implementation address', async function () {
        await this.Proxy.upgradeTo(this.ERC20V2.address, { from: admin });


Silly me, I forgot to include the data object key...

let result = await web3.eth.call({
            from: user,
            to: this.MyContract.address,
            data: functionData //forgot data:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.