I am trying to create a new token contract using the ERC20 Upgradeable contracts provided from Openzeppelin (OZ). I created the token contract and then made some basic unit tests. However, in one of the tests, I expect to see "TransparentUpgradeableProxy: admin cannot fallback to proxy target", when calling an implementation method from the Proxy admin. In one of my projects it works fine, but in another made from scratch - it does not. The error that I receive is "Error: missing revert data in call exception; Transaction reverted without a reason string..."
Here is the code for the one that is failing:
// the contract ---
contract TokenName is ERC20PresetMinterPauserUpgradeable {
/**
* @dev Used to prevent implementation manipulation
*/
constructor() initializer {}
/**
* @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE`, `PAUSER_ROLE` to the owner
* specified in the owner param.
* @param owner is the owner of the contract after initialization
* See {ERC20-constructor}.
*/
function initialize(string memory name, string memory symbol, address owner) public initializer {
require(owner != address(0x0), "New owner cannot be 0");
__ERC20PresetMinterPauser_init(name, symbol);
__ERC20_init(name, symbol);
_setupRole(DEFAULT_ADMIN_ROLE, owner);
_setupRole(MINTER_ROLE, owner);
_setupRole(PAUSER_ROLE, owner);
revokeRole(PAUSER_ROLE, _msgSender());
revokeRole(MINTER_ROLE, _msgSender());
revokeRole(DEFAULT_ADMIN_ROLE, _msgSender());
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override(ERC20PresetMinterPauserUpgradeable) {
ERC20PresetMinterPauserUpgradeable._beforeTokenTransfer(from, to, amount);
}
}
// token unit tests ---
describe.only("Upgradeable token tests", function () {
let accounts;
let deployer;
let owner;
let tokenInstance;
let tokenName = "Token Name";
let tokenSymbol = "symbol";
const tokenAmount = '10000000000000000000';
before(async function() {
accounts = await ethers.getSigners();
deployer = accounts[0];
owner = accounts[1];
user = accounts[2];
})
it("should throw error when calling the implementation contract with the proxy admin", async function() {
const tokenContract = await ethers.getContractFactory(tokenSymbol);
tokenInstance = await upgrades.deployProxy(tokenContract, [tokenName, tokenSymbol, owner.address], { initializer: 'initialize(string,string,address)', unsafeAllow: ['constructor'] });
await tokenInstance.deployed();
console.log("default admin addr: ", await upgrades.erc1967.getAdminAddress(tokenInstance.address));
console.log("token instance addr: ", tokenInstance.address);
await upgrades.admin.changeProxyAdmin(tokenInstance.address, owner.address);
console.log("changed admin addr: ", await upgrades.erc1967.getAdminAddress(tokenInstance.address));
expect(await upgrades.erc1967.getAdminAddress(tokenInstance.address)).to.equal(owner.address);
//console.log("tokenInstance", tokenInstance);
console.log("owner addr: ", owner.address);
console.log("deployer addr: ", deployer.address);
console.log("admin changed ---");
await expect(tokenInstance.connect(owner).name()).to.be.revertedWith('TransparentUpgradeableProxy: admin cannot fallback to proxy target');
});
})
There is no much need to add the code of the other project, because it is exactly the same, but I will do in order to have it as comparison.
// token contract ---
contract MarsCoin is ERC20PresetMinterPauserUpgradeable {
uint counter;
function increment() public {
counter += 1;
}
function getCounter() public view returns (uint) {
return counter;
}
/**
* @dev Used to prevent implementation manipulation
*/
constructor() initializer {}
/**
* @dev Grants `DEFAULT_ADMIN_ROLE`, `MINTER_ROLE`, `PAUSER_ROLE` to the owner
* specified in the owner param.
* @param owner is the owner of the contract after initialization
* See {ERC20-constructor}.
*/
function initialize(string memory name, string memory symbol, address owner) public initializer {
require(owner != address(0x0), "New owner cannot be 0");
counter = 0;
__ERC20PresetMinterPauser_init(name, symbol);
__ERC20_init(name, symbol);
_setupRole(DEFAULT_ADMIN_ROLE, owner);
_setupRole(MINTER_ROLE, owner);
_setupRole(PAUSER_ROLE, owner);
revokeRole(PAUSER_ROLE, _msgSender());
revokeRole(MINTER_ROLE, _msgSender());
revokeRole(DEFAULT_ADMIN_ROLE, _msgSender());
}
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override(ERC20PresetMinterPauserUpgradeable) {
ERC20PresetMinterPauserUpgradeable._beforeTokenTransfer(from, to, amount);
}
}
// token unit tests ---
const { expect } = require("chai");
const { ethers, upgrades } = require("hardhat");
describe.only("Mars Coin tests", function () {
let accounts;
let deployer;
let owner;
let tokenInstance;
let tokenName = "Mars Coin";
let tokenSymbol = "MARS";
const tokenAmount = '10000000000000000000';
before(async function() {
accounts = await ethers.getSigners();
deployer = accounts[0];
owner = accounts[1];
user = accounts[2];
})
it("Should deploy MARS token with correct owner set", async function () {
const MarsCoin = await ethers.getContractFactory("MarsCoin");
tokenInstance = await upgrades.deployProxy(MarsCoin, [tokenName, tokenSymbol, owner.address], { initializer: 'initialize(string,string,address)', unsafeAllow: ['constructor'] });
await tokenInstance.deployed();
const adminRole = await tokenInstance.DEFAULT_ADMIN_ROLE();
const minterRole = await tokenInstance.MINTER_ROLE();
const pauserRole = await tokenInstance.PAUSER_ROLE();
expect(await tokenInstance.name()).to.equal(tokenName);
expect(await tokenInstance.symbol()).to.equal(tokenSymbol);
expect(await tokenInstance.hasRole(adminRole, deployer.address)).to.equal(false);
expect(await tokenInstance.hasRole(minterRole, deployer.address)).to.equal(false);
expect(await tokenInstance.hasRole(pauserRole, deployer.address)).to.equal(false);
expect(await tokenInstance.hasRole(adminRole, owner.address)).to.equal(true);
expect(await tokenInstance.hasRole(minterRole, owner.address)).to.equal(true);
expect(await tokenInstance.hasRole(pauserRole, owner.address)).to.equal(true);
});
it("Should mint tokens to user account", async function() {
const tokenInstanceWithOwner = tokenInstance.connect(owner);
await tokenInstanceWithOwner.mint(user.address, tokenAmount);
const accountBalance = (await tokenInstance.balanceOf(user.address)).toString();
expect(accountBalance).to.equal(tokenAmount)
})
it("Should try to call contract implementation contract with deployer", async function () {
const tokenInstanceWithDeployer = tokenInstance.connect(deployer);
expect(await tokenInstanceWithDeployer.name()).to.equal(tokenName)
})
it("Should change the MARS token proxy admin correctly", async function() {
await upgrades.admin.changeProxyAdmin(tokenInstance.address, owner.address);
expect(await upgrades.erc1967.getAdminAddress(tokenInstance.address)).to.equal(owner.address);
})
it.only("Should throw on trying to call contract implementation from new proxy admin owner", async function () {
const MarsCoin = await ethers.getContractFactory("MarsCoin");
tokenInstance = await upgrades.deployProxy(MarsCoin, [tokenName, tokenSymbol, owner.address], { initializer: 'initialize(string,string,address)', unsafeAllow: ['constructor'] });
await tokenInstance.deployed();
await upgrades.admin.changeProxyAdmin(tokenInstance.address, owner.address);
expect(await upgrades.erc1967.getAdminAddress(tokenInstance.address)).to.equal(owner.address);
await expect(tokenInstance.connect(owner).name()).to.be.revertedWith('TransparentUpgradeableProxy: admin cannot fallback to proxy target');
})
});
Keep in mind the .only test that I am running, thus, the rest can be skipped, but I just paste it to have the exact code.
Okay, I succeeded to fix this by just creating the project from scratch.
npm init
-> then follow the basic instructionsnpm install hardhat
npx hardhat init
-> create an advanced sample project and follow the instructionsnpm install
- all the necessary packages that are missing (some of the existing ones were re-installed with older version, because it seems the problem comes from a newer version library)Very interesting stuff.. previously I just tried to delete all the folders like .cache, node_module, the built folder, package-lock.json and then to hit the
npm install
, but without success. I could not understand what was exactly the issue.However, these are the packages that I am using: