Web3 send fails but OpenZeppelin send-tx works?

:computer: Environment
I am using a private blockchain with Besu Hyperledger and the IBFT 2.0 (Proof of Authority) consensus protocol
I am using NodeJs with: Web3.js, @openzeppelin/contract-loader and @truffle/hdwallet-provider
The contract is upgradable and was deployed with OpenZeppelin deploy function.

:memo:Details

Hello to all,

I am having a very strange problem with Web3.js and my contract.
In my contract I have a function addCompany(bytes32 _id, address initialAdmin) external onlyOwner returns (bool) which basically adds a company with its admin address to the storage of the contract.
If I call this function using npx oz send-tx it always works regardless of the companyId or the address I put for the initialAdmin.
The problem appears when I do it programmatically with the following code:

async function addNewCompany(id, adminAddress) {

const bytes32ID = web3.utils.asciiToHex(id)
const tx = await companiesContract.methods.addCompany(bytes32ID, adminAddress).send({from: 
              sender})

return tx.transactionHash
}

There is two cases:

  1. I call the function with a company Id and a newly generated address (using ethers.Wallet.createRandom) and I get an Error: Transaction has been reverted by the EVM…
  2. I generate the address, call the function once using the openzeppelin send-tx function (working) and after that, I can use the same admin address also via node.js

I have really little idea of what is going on. It is like Web3.js is missing one step which is instead done when using openzeppelin send-tx.

I hope someone can / knows how to help.

Let me know if you need more details.

1 Like

Hi @jeeza1,

That does sound strange. I assume that you are initializing the upgradeable contract.

You may want to try running the following simple example to see if that generates the issue to help track this down. I wonder if it is gas, otherwise is it EVM version.

MyContract.sol

//SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;

import "@openzeppelin/contracts-ethereum-package/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol";

contract MyContract is Initializable, OwnableUpgradeSafe {

    event DoneStuff();

    function initialize() public initializer {
        __Ownable_init();
    }

    function doStuff(address someAddress) public onlyOwner {
        emit DoneStuff();
    }
}

Deploy

$ npx oz deploy
✓ Compiled contracts with solc 0.6.12 (commit.27d51765)
Compilation warnings:
@openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.

@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.

@openzeppelin/contracts-ethereum-package/contracts/access/Ownable.sol: Warning: SPDX license identifier not provided in source file. Before publishing, consider adding a comment containing "SPDX-License-Identifier: <SPDX-License>" to each source file. Use "SPDX-License-Identifier: UNLICENSED" for non-open-source code. Please see https://spdx.org for more information.

contracts/MyContract.sol:15:22: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.
    function doStuff(address someAddress) public onlyOwner {
                     ^-----------------^

? Choose the kind of deployment upgradeable
? Pick a network development
? Pick a contract to deploy MyContract
✓ Added contract MyContract
✓ Contract MyContract deployed
All implementations have been deployed
? Call a function to initialize the instance after creating it? Yes
? Select which function * initialize()
✓ Setting everything up to create contract instances
✓ Instance created at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
To upgrade this instance run 'oz upgrade'
0xCfEB869F69431e42cdB54A4F4f105C19C080A601

Based on the Learn guide:
https://docs.openzeppelin.com/learn/deploying-and-interacting#interacting-programmatically

index.js

// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');

async function main() {
  // Set up web3 object, connected to the local development network, and a contract loader
  const web3 = new Web3('http://localhost:8545');
  const loader = setupLoader({ provider: web3 }).web3;

  // Set up a web3 contract, representing our deployed MyContract instance, using the contract loader
  const address = '0xCfEB869F69431e42cdB54A4F4f105C19C080A601';
  const myContract = loader.fromArtifact('MyContract', address);

  // Retrieve accounts from the local node, we'll use the first one to send the transaction
  const accounts = await web3.eth.getAccounts();

  const someAddress = '0x1df62f291b2e969fb0849d99d9ce41e2f137006e';

  // Send a transaction to doStuff()
  const tx = await myContract.methods.doStuff(someAddress).send({ from: accounts[0], gas: 50000, gasPrice: 1e6 });
  console.log(tx);
}

main();

Run index.js

$ node src/index.js
{ transactionHash:
   '0x35fff93067437dad2ac9434eb1a8d82071f4c4cc1f3d020b2ddb8447b0c80e70',
  transactionIndex: 0,
  blockHash:
   '0xca44f5b95c9937ac6b5941cd32256b266860d23b178821a6a37a34981385cac0',
  blockNumber: 4,
  from: '0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1',
  to: '0xcfeb869f69431e42cdb54a4f4f105c19c080a601',
  gasUsed: 26148,
  cumulativeGasUsed: 26148,
  contractAddress: null,
  status: true,
  logsBloom:
   '0x00000000000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000400000001000000000000004000000000000000000000000000000000000000000000000000000',
  events:
   { DoneStuff:
      { logIndex: 0,
        transactionIndex: 0,
        transactionHash:
         '0x35fff93067437dad2ac9434eb1a8d82071f4c4cc1f3d020b2ddb8447b0c80e70',
        blockHash:
         '0xca44f5b95c9937ac6b5941cd32256b266860d23b178821a6a37a34981385cac0',
        blockNumber: 4,
        address: '0xCfEB869F69431e42cdB54A4F4f105C19C080A601',
        type: 'mined',
        id: 'log_5373efdd',
        returnValues: Result {},
        event: 'DoneStuff',
        signature:
         '0xb5a7dd393f3ac16b00dc254f20234aa9070f4e7a82d61c5e1930a633d07768a1',
        raw: [Object] } } }

Hello and thanks for your reply.
I have tried your code and it does work, even if I take the address someAddress and store it in some variable.
At this point, I believe there is a mistake in my smart contract:

pragma solidity ^0.6.0;
import "./Permissions.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/access/Ownable.sol";
import "@openzeppelin/contracts-ethereum-package/contracts/Initializable.sol";

contract Companies is Initializable, OwnableUpgradeSafe {

    struct Entity {
        address adr;
        uint256 permissions;
        uint256 indexInArray;
    }

    struct Company {
        bytes32 id;
        uint256 indexInArray;
        address[] entityAddresses;
        mapping (address => Entity) entities;
    }

    /**
    * @dev mapping containing all registered companies
    */
    mapping (bytes32 => Company) public companies;
    /**
     * @dev Array containing all of the active companies
     */
    bytes32[] companyIds;

    /**
     * @dev top level mapping containing entity to company reference
    */
    mapping (address => bytes32) public entityToCompany;

    function initialize() public initializer {
        OwnableUpgradeSafe.__Ownable_init();
    }

    /**
    * @dev adds a new company in the companies mapping with initial admin
    */
    function addCompany(bytes32 _id, address initialAdmin) external onlyOwner returns (bool) {
        require(_id != bytes32(0), "company id is required");
        require(companies[_id].id == bytes32(0), "Company already exists");

        companies[_id].id = _id;
        companies[_id].entities[initialAdmin] = Entity(initialAdmin, Permissions.modifyEntities(), 0);
        companies[_id].entityAddresses.push(initialAdmin);
        companies[_id].indexInArray = companyIds.length;
        companyIds.push(_id);
        entityToCompany[initialAdmin] = _id;

        return true;
    }
}

And this is the Permissions.sol library:

library Permissions {

/**
 * @dev Whether the entity can modify company's entities
 */
function modifyEntities () external pure returns (uint256) {
    return 1;
}

/**
 * @dev Whether the entity can add new certificates
 */
function addCertificates () external pure returns (uint256) {
    return 2;
}

/**
 * @dev Whether the entity can transfer existing certificate
 */
function transferCertificate () external pure returns (uint256) {
    return 4;
}

/**
 * @dev Whether the entity can assign tests for certificates
 */
function assignTests () external pure returns (uint256) {
    return 8;
}
}

What I don’t understand also is why it does work when calling the same function using the openzeppelin cli :confused:

Maybe I am not handling the struct and storage layer properly being it an upgradable contract?
Any idea?

EDIT

After some testing I found out that if I remove this line from the smart contract
companies[_id].entities[initialAdmin] = Entity(initialAdmin, 1, 0);
then everything works fine.

Why is that?
And why does that work if I call it using send-tx from the cli?

Thanks for your time, this is the last thing I need to solve before going live! :confused:

1 Like

I solved the issue by calling that specific method with a huge amount of gas:
const tx = await companiesContract.methods.addCompany(bytes32ID, adminAddress).send({from: sender, gas: 500000})

This does seems like a hack to be honest. Would be nice to understand what is actually wrong instead.

1 Like

Hi @jeeza1,

Sorry it has taken a while to get back to you.

When I run the following script, I get it using just under 200,000 gas.
I assume it is the storage being used.

As for OpenZeppelin CLI it will default to the gas limit specified in the configuration file which is why you weren’t having issues there.

index.js

// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');

async function main() {
  // Set up web3 object, connected to the local development network, and a contract loader
  const web3 = new Web3('http://localhost:8545');
  const loader = setupLoader({ provider: web3 }).web3;

  // Set up a web3 contract, representing our deployed contract instance, using the contract loader
  const address = '0x254dffcd3277C0b1660F6d42EFbB754edaBAbC2B';
  const companies = loader.fromArtifact('Companies', address);

  // Retrieve accounts from the local node, we'll use the first one to send the transaction
  const accounts = await web3.eth.getAccounts();

  // Send a transaction to doStuff()
  const tx = await companies.methods.addCompany("0x01", accounts[1]).send({ from: accounts[0], gas: 200000, gasPrice: 1e6 });
  console.log(tx);
}

main();

Running the script

I get gasUsed: 195104

$ node ./src/index.js
{ transactionHash:
   '0xa12944d7ba77d21ccca5f611cf4f5084b5642cf3a8ee1c29e27647bb9078a1db',
  transactionIndex: 0,
  blockHash:
   '0x8bcce1f830571731e95ed8da33cda62ef053cc796ffcef298b89aba37ef0d4f0',
  blockNumber: 5,
  from: '0x90f8bf6a479f320ead074411a4b0e7944ea8c9c1',
  to: '0x254dffcd3277c0b1660f6d42efbb754edababc2b',
  gasUsed: 195104,
  cumulativeGasUsed: 195104,
  contractAddress: null,
  status: true,
  logsBloom:
   '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000',
  events: {} }