[Question] [Hardhat] Best Way Call OpenZeppelin's Upgradeable Contract

:computer: Environment

  • Private Quorum Network with Raft Consensus
  • “devDependencies”: {
    @nomiclabs/hardhat-ethers”: “^2.0.2”,
    @openzeppelin/hardhat-upgrades”: “^1.7.0”,
    “ethers”: “^5.1.4”
    },
    “dependencies”: {
    @openzeppelin/contracts-upgradeable”: “^4.1.0”
    }

:memo:Details
I am very new to OpenZeppelin and thanks for building such a awesome tool. my questions is
I use hardhat-upgrades plugin to deploy a upgradable contract:MyToken.sol and I believe the deployment is working since I can use the deployed proxy instance to trigger function (below code works for me):

const MyTokenContract = await ethers.getContractFactory("MyToken");
const contract = await upgrades.deployProxy(MyTokenContract);
await contract.deployed();
const contractName = await contract.name();

Also I can get below info from this deployment:

  1. Proxy contract’s address
  2. ABI of upgradable contract (MyToken.sol)

After that, I store the proxy address and contract abi in a database b/c for my case, I need to use a nodeJS application leverage that proxy contract address, MyToken’s abi and user’s keystore to call that upgradable contract’s function through the proxy contract.

I try the ethers’s new ethers.Contract( address , abi , signerOrProvider) to init that upgradable contract instance and call the function, but it looks like I failed to use proxy contract address and MyToken.sol 's abi to do it. I get a error when I trigger the contract(MyToken.sol) function:

Error: call revert exception (method="name()", errorSignature=null, errorArgs=[null], reason=null, code=CALL_EXCEPTION, version=abi/5.1.2)
at Logger.makeError (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/logger/src.ts/index.ts:205:28)
at Logger.throwError (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/logger/src.ts/index.ts:217:20)
at Interface.decodeFunctionResult (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/abi/src.ts/interface.ts:326:23)
at Contract.<anonymous> (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/contracts/src.ts/index.ts:321:44)
at step (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/contracts/lib/index.js:48:23)
at Object.next (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/contracts/lib/index.js:29:53)
at fulfilled (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/contracts/lib/index.js:20:58)
at processTicksAndRejections (internal/process/task_queues.js:93:5)
at runNextTicks (internal/process/task_queues.js:62:3)
at listOnTimeout (internal/timers.js:523:9) {

reason: null,

So the questions is

  1. is that the right way to implement call a upgradeable contract via proxy address ,abi and signer ?
  2. For my current approach, I feel like I should get the proxy’s abi rather than the implementation contract’s abi, please suggest how to fix it.

:1234: Code to reproduce

  1. Token.sol

    contract MyToken is ERC20Upgradeable, OwnableUpgradeable {
     event Mint(address indexed _account, uint256  _amount);
     event Burn(address indexed _account, uint256  _amount);
    
     function initialize() initializer public {
         __ERC20_init("MyToken", "MTK");
     }
    
     function decimals() public pure override returns (uint8) {
         return 18;
     }
     function transfer(address recipient, uint256 amount) public override returns (bool) {
         // TODO: add access permission check
         return super.transfer(recipient, amount);
     }
     function balanceOf(address acccount) public view override returns (uint256){
         // TODO: add access permission check
         return super.balanceOf(acccount);
     }
    
     /** Below are admin functions **/
     function transferFrom(address sender, address recipient, uint256 amount) public override onlyOwner returns (bool) {
         // TODO: add access permission check
         // TODO: add balance check
         _transfer(sender, recipient, amount);
    
         return true;
     }
     function mint(address account, uint256 amount) public onlyOwner {
         // TODO: add access permission check
         _mint(account, amount);
         emit Mint(account, amount);
     }
     function burn(address account, uint256 amount) public onlyOwner {
         // TODO: add access permission check
         _burn(account, amount);
         emit Burn(account, amount);
     }
    

    }

  2. deploy contract

    const MyTokenContract = await ethers.getContractFactory(“MyToken”);
    const contract = await upgrades.deployProxy(MyTokenContract);
    await contract.deployed();
    const contractName = await contract.name();// this is working
    console.log(“Deployed Proxy Contract address:”, contract.address);

  3. Call contract via ethers

    const contract = new ethers.Contract(proxyContractAddress, abi, signer)
    const name = await contract.name(); // not work

I just try to use below code to get the the proxy contract’s abi and test this abi with

const MyTokenContract = await ethers.getContractFactory(“MyToken”);
const contract = await upgrades.deployProxy(MyTokenContract);
await contract.deployed();
const abi = contract.interface.format(FormatTypes.json) // get proxy contract abi

but got same error with below code

const contract = new ethers.Contract(proxyContractAddress, abi, signer) // use the abi from proxy
const name = await contract.name(); // not work

Is your ABI in this step correct, or associated to the old contract?

1 Like

@Yoshiko Thanks for the help, actually In my case, I have not upgraded the contract yet so just only 1 version contract and abi and I am not sure if my abi is correct or not, just let you know I tried the abi from compiled json file and contract.interface.format(FormatTypes.json), same result.

Also my latest update is

If I use below code, I can get the contract instance and make it work:

async function deploy() {    
    const MyTokenContract = await ethers.getContractFactory("DropToken");
    const contractProxy = await upgrades.deployProxy(MyTokenContract);
    console.log(MyTokenContract.signer.address);
    await contractProxy.deployed();

  }
  async function call() {
    const MyTokenContract = await ethers.getContractFactory("DropToken");
    const contract = MyTokenContract.attach('0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0');
    console.log("Say hi,:", await contract.name());
  }

  async function main() {
    await deploy();
    await call();
  }

But if I break the execution of deploy() and call() function into 2 times, it will NOT work
run below code to deploy contract first:

async function deploy() {    
    const MyTokenContract = await ethers.getContractFactory("DropToken");
    const contractProxy = await upgrades.deployProxy(MyTokenContract);
    console.log(MyTokenContract.signer.address);
    await contractProxy.deployed();

  }
  async function main() {
    await deploy();
  }

Then run below to call the functions

  async function call() {
    const MyTokenContract = await ethers.getContractFactory("DropToken");
    const contract = MyTokenContract.attach('0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0');
    console.log("Say hi,:", await contract.name());
  }

  async function main() {
    await call();
  }

Then got call revert exception error:

Error: call revert exception (method="name()", errorSignature=null, errorArgs=[null], reason=null, code=CALL_EXCEPTION, version=abi/5.1.2)
    at Logger.makeError (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/logger/src.ts/index.ts:205:28)
    at Logger.throwError (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/logger/src.ts/index.ts:217:20)
    at Interface.decodeFunctionResult (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/abi/src.ts/interface.ts:326:23)
    at Contract.<anonymous> (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/contracts/src.ts/index.ts:321:44)
    at step (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/contracts/lib/index.js:48:23)
    at Object.next (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/contracts/lib/index.js:29:53)
    at fulfilled (/Users/mbao010/Documents/gitbase/pwc/drip/drip-contract-v2/node_modules/@ethersproject/contracts/lib/index.js:20:58)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at runNextTicks (internal/process/task_queues.js:62:3)
    at listOnTimeout (internal/timers.js:523:9) {
  reason: null,
  code: 'CALL_EXCEPTION',
  method: 'name()',
  errorSignature: null,
  errorArgs: [ null ],
  address: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0',
  args: [],
  transaction: {
    data: '0x06fdde03',
    to: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0',
    from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    gasLimit: BigNumber { _hex: '0xaf09a8', _isBigNumber: true }
  }
}

I compare above 2 cases, the difference is the contract.signer.provider from those 2 cases are a little different, but I have no idea what that mean to me.

Also do you have any success sample code snippet for my case (also I believe this is a very generic usecase) for me to reference ? Thanks in advance.

Hi, welcome! :wave:

I think maybe you can have a look at this tutorial:

2 Likes

Agree with Skyge, this tutorial will help you understand how to use proxies.

2 Likes

Thanks @Yoshiko and @Skyge I have figured out this problem.
The root cause is when I deploy the contract I did not specify the network parameter in my command which make my deployment happened on Hardhat’s built-in network(https://hardhat.org/hardhat-network/#hardhat-network) rather than my quorum network, I got it working after I change deployment point to local quorum network. Many thanks for the help!

But a follow up concern is: does the OpenZeppelin‘s hardhat plugin work well with Hardhat Network ?

Glad to hear that.

Yes, I think so.

1 Like