1

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 2

0

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.

2
  • Hi Rob, First of all, thanks for the quick answer. It's good to precise the difference between inheritance and instatiating, but I wasn't making that confusion; it just happens that my contract from which I want to call contract A's functions, also happens to be inheriting from it. I'll edit my question to make that clearer. The issue remains: why does it work when I pass a bytes32[] with only one bytes32 in it, and not more? Is it a problem in my solidity code (I think not, the function addStuff works fine), my js code (same here, even estimateGas works), or is it a bug in testrpc?
    – Hillfias
    Nov 28, 2017 at 16:22
  • Hi Rob, I've edited my question to make it clearer what my issue is. I've tried out your code in Remix and it works perfectly. I also tried mine out (had to learn to use Remix, never tested anything with it before, but it's actually intelligent to do that haha) and everything works fine too... Problem must be in testrpc then I guess? (I've encountered weird stuff before and I've read about people complaining about testrpc not being maintained anymore and having some glitches) Could you reproduce this behaviour and tell me if it's just on my side? (Or tell me if anything else is wrong)
    – Hillfias
    Nov 29, 2017 at 16:22
0

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.