TransWikia.com

Invalid Op Code - passing bytes32 array to a contract method called from another method of another contract

Ethereum Asked by Hillfias on January 28, 2021

I’ve been banging my head on this one for some hours, I’d appreciate any help 🙂

This is a final edit to clearly state the issue: Is there a proper way to pass a bytes32 array to a method on contract B, that itself instantiates a contract A (from its address) and passes the bytes32 array to a method of that contract? Passing the array directly to contract A’s method works just fine, but passing it to contract B’s method which in turn passes it to contract A’s method doesn’t work when the array has more than one bytes32.

Is that a limitation of solidity? Or am I doing something wrong (in solidity or with my javascript web3js code)? Or is that a bug with testrpc?

Full details below. Let me know if you can reproduce the problem, or if that works just fine or whatnot.


I have a contract A and a contract B which inherits from it. What I want to do is instantiate a contract A in contract B, from an already deployed contract A address, and call that instance’s methods.

I know how to do this (or at least, I think I do; maybe it’s not a proper way to do it?) and it works. The real issue is with the array of bytes32.

Context

Before anything else, here’s the contracts code.

contract A {
  struct MyStruct {
    address user;
    bytes32 [] stuff;
  }

  MyStruct [] public myStructs;

  function addStuff (bytes32 [] _stuff) public {
    myStructs.push (
      MyStruct ({user: msg.sender, stuff: _stuff})
    );
  }
}

contract B is A {
  function provideStuff (address to, bytes32 [] stuff) public {
    A(to).addStuff(stuff);
  }
}

A bit of context: stuff is a hexadecimal string (which can be 256 bits = 32 bytes (fits perfectly in a bytes32), or more (512, etc.); hence the array bytes32[]).

First, I deploy a contract A (returning an instance contractA) with the wallet address anAddress. This gives me, when my contract is deployed, the smart contract address contractAaddress.

Calling addStuff with an array of bytes32 works perfectly:

gasEstimate = contractA.addStuff.estimateGas(getBytes32Array(stuff), {from: anAddress);

contractA.addStuff(getBytes32Array(stuff), {from: anAddress, gas: gasEstimate});

Note – getBytes32Array

I have a function, getBytes32Array, which outputs an array of Bytes32 (hexadecimal strings actually) from a given hexadecimal string:

function getBytes32Array(string){
   var splittedString = string.match(/.{1,64}/g);

   for(var i=0; i< splittedString.length; i++){
      splittedString[i] = "0x"+splittedString[i];
   }

   return splittedString;
}

This allows me to simply input my hex string, for instance e0f89ca8eae95281590977802df657506a151304234d15570c12cc26263a8b7a2bf140bc9f80baa93879634717d9c2cf08cc6c96492c1b56c053ae54546f4f20 (512 bits) and obtain ["0xe0f89ca8eae95281590977802df657506a151304234d15570c12cc26263a8b7a","0x2bf140bc9f80baa93879634717d9c2cf08cc6c96492c1b56c053ae54546f4f20"] which will be understood as a bytes32 array containing two bytes32.


On to the real issue

Calling addStuff in this manner works perfectly with an array of bytes32 containing more than one bytes32.

Now, second, I’m deploying my contract B (returning an instance contractB) with the wallet address anotherAddress. This gives me, when my contract is deployed, the smart contract address contractBaddress.

What I’d like to do is call the provideStuff method of contract B, feeding it the address of the deployed contract A contractAaddress and the stuff, which should instantiate the contract A from the address and call the addStuff with the stuff provided. (see contract B solidity code)

gasEstimate = contractB.provideStuff.estimateGas(contractAaddress, getBytes32Array(stuff), {from: anotherAddress});

contractB.provideStuff(contractAaddress, getBytes32Array(stuff), {from: anotherAddress, gas: gasEstimate});

But this gives me an invalid Op Code each time… Despite the estimateGas working (isn’t that weird?)… This does work when my bytes32 array only has one bytes32. Even weirder, it also works when it has a second bytes32 that’s a very short hex string (see PS1). But I can’t get a consistent behaviour with an array of x bytes32, each 32 bytes (64 character) hex strings (or even shorter)…

Thanks in advance.

PS1: the previous example works fine if stuff is a 256bit hex value or a little longer (I tried with hardcoding ["0x256BitValueHere","0xverySmallValueHere"], 0xverySmallValueHere can’t be longer than a few characters (maybe 10, I don’t remember), after that it fires invalide Op Code again. I also tried with several little values ["0xverySmallValueHere", "0xverySmallValueHere", "0xverySmallValueHere", "0xverySmallValueHere"], it won’t work either.

PS2: working locally with testrpc.


EDIT: Everything works perfectly in Remix. So it’s not a Solidity code issue.

2 Answers

I just tried your contract in Remix (Solidity version 0.4.24) and it works fine. I can write stuff via method provideStuff of contract B and then read it via contract A. Though, I needed to add the following method into contract A for reading stuff from it:

function get (uint i, uint j) public view returns (bytes32) {
  return myStructs [i].stuff [j];
}

because default getter generated by Solidity does not read dynamic arrays.

Answered by Mikhail Vladimirov on January 28, 2021

You're confusing "instantiating" contract A with "inheriting from" contract A.

Inheritance is more like class libraries where B takes on the state variables and functions of A. There is no separate A to talk to in the case that contract B is A - only B that happens to include source code from A.

Since you want two contracts to talk to each other, first deploy A, then talk about it in B. The easiest method is for B's source file to include a copy of A so the compiler can "see it" and understand the ABI (function sigs). There is no B is A but you can cast a variable to "be" type contract A, like this:

A a; (sort of like uint x).

Now that the "type" of "a" is established, the other important factor is its address. Assuming you have already deployed A somewhere, you should know its address. You can pass that into B's constructor and finish instantiating "a".

function B(address aAddress) public {
  a = A(aAddress); // so "a" is the "A" found at "aAddress"
}

Great. Now you can send messages back and forth with a.function(args).

Here's a little tinkering similar to yours:

pragma solidity 0.4.17;

contract A {

    event LogTest(address sender, bytes32 element);

    function test(bytes32[] X) public returns(bool success) {

        // This unbounded loop is an anti-pattern, but it shows it working at small scales.

        for(uint i=0; i<X.length; i++) {
            LogTest(msg.sender, X[i]);
        }

        return true;
    }
}

contract B {

    A a; // variable "a" cast as type "A" which is the contract shown above

    function B(address aAddress) public {
        a = A(aAddress); // instantiate an instance of "a" already deployed at the address passed in
    }

    function testIt(bytes32[] array) public returns(bool success) {
        return a.test(array); // send a message to contract "a"
    }
}

You can play around in Remix to see it working.

  1. Deploy A
  2. Copy A's address
  3. Deploy B, pasting A into the constructor
  4. Try B.test with ["0x1","0x2"] to pass it an array.

Hope it helps.

Answered by Rob Hitchens on January 28, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP