Mocking upgradeable contracts in unit tests

Hello,

I am wondering how can I go about mocking upgradeable contracts? Am I able to use libraries such as https://github.com/gnosis/mock-contract still or does OZ provide some mock helper?

Thanks in advance

1 Like

Hi @emobe,

There isn’t a mock helper as part of OpenZeppelin.
In OpenZeppelin Contracts, mock contracts are manually generated.

I haven’t used https://github.com/gnosis/mock-contract. If you are using a mock library, you may want to write unit tests that test just the logic contracts and have additional tests for testing as upgradeable contracts.

1 Like

True, I can make the logic a separate contract. Would make it more SOLID.

Is there any reason not to use an upgradeable contract?

1 Like

Hi @emobe,

I mean that for the one contract, you can create tests for the logic and tests for the upgradeability.

A very simple example is shown below (using OpenZeppelin Test Environment):

Counter.sol

pragma solidity ^0.5.0;

contract Counter {
  uint256 public value;

  function increase() public {
    value++;
  }
}

Counter.test.js

const { accounts, contract, web3 } = require('@openzeppelin/test-environment');

const { TestHelper } = require('@openzeppelin/cli');
const { Contracts, ZWeb3 } = require('@openzeppelin/upgrades');
const {
  BN,           // Big Number support
  constants,    // Common constants, like the zero address and largest integers
  expectEvent,  // Assertions for emitted events
  expectRevert, // Assertions for transactions that should fail
} = require('@openzeppelin/test-helpers');


ZWeb3.initialize(web3.currentProvider);

const Counter = Contracts.getFromLocal('Counter');

require('chai').should();

describe('Counter (upgradeable)', function () {

  beforeEach(async function () {
    this.project = await TestHelper();
    this.proxy = await this.project.createProxy(Counter);
  })

  it('should have a value', async function () {
    const result = await this.proxy.methods.value().call();
    result.should.be.bignumber.equal('0');
  })

  it('should increase value', async function () {
    await this.proxy.methods.increase().send();
    const result = await this.proxy.methods.value().call();
    result.should.be.bignumber.equal('1');
  })
})

Counter.logic.test.js

const { accounts, contract, web3 } = require('@openzeppelin/test-environment');

const {
  BN,           // Big Number support
  constants,    // Common constants, like the zero address and largest integers
  expectEvent,  // Assertions for emitted events
  expectRevert, // Assertions for transactions that should fail
} = require('@openzeppelin/test-helpers');

const Counter = contract.fromArtifact('Counter');

require('chai').should();

describe('Counter (logic)', function () {

  beforeEach(async function () {
    this.counter = await Counter.new();
  })

  it('should have a value', async function () {
    const result = await this.counter.value();
    result.should.be.bignumber.equal('0');
  })

  it('should increase value', async function () {
    await this.counter.increase();
    const result = await this.counter.value();
    result.should.be.bignumber.equal('1');
  })
})
1 Like

Aside from the benefits of using OpenZeppelin SDK upgradeable contracts, some reasons to not create a contract as upgradeable:

  1. There is a gas overhead for using a Proxy.
  2. OpenZeppelin SDK contracts (post Istanbul) can’t receive Ether from other contracts via transfer: OpenZeppelin upgradeable contracts affected by Istanbul hardfork
  3. Some entity needs to control upgrading a contract. Whether this is a single account, a multi-sig, a DAO or some other mechanism. (See Upgrades governance for using a multi-sig). This process for upgrading should be clearly explained to users under what circumstances it can occur.
1 Like

Ah yes, makes sense now as it doesn’t need to be initialised in order to be tested. Thanks for your help

1 Like

Hi @emobe,

We still need to call any initializers in logic and upgradeable tests, to ensure that the contract is initialized.

1 Like