10

I am trying to pass a struct as an argument while calling the function of another contract:

pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;

library Test { 
  struct TStruct {
    int x;
    int y;
  }   
}

contract A {

  function SetStruct (Test.TStruct sin) public returns (int) {
    data = 10;
    Test.TStruct memory s;
    s.x = sin.x;
    s.y = sin.y;
    return s.x;
  }
}

contract B {
  address public BAddr;

  function TalkToA () public {
    BAddr = new A();
    Test.TStruct memory sin = Test.TStruct(10, 5);
    BAddr.call(bytes4(keccak256("SetStruct((int256,int256))")), sin);
  }
}

Invoking SetStruct directly from Remix causes it to correctly execute. However, when I deploy contract B, the call to SetStruct does not go through. What is the correct way of passing a struct as a parameter in a call invocation?

3 Answers 3

15

By using abi.encodeWithSignature, your struct successfully to the low-level call.

The code below is modified based on what you provided.

pragma solidity ^0.4.24;
pragma experimental ABIEncoderV2;

library Test { 
  struct TStruct {
    int x;
    int y;
  }   
}

contract A {

  function SetStruct (Test.TStruct sin) public returns (int) {
    Test.TStruct memory s;
    s.x = sin.x;
    s.y = sin.y;
    return s.x;
  }
}

contract Bnew {
  address public BAddr;
  bool public success;

  function TalkToA () public returns (bool) {
    BAddr = new A();
    Test.TStruct memory sin = Test.TStruct(10, 5);
    bytes memory data = abi.encodeWithSignature("SetStruct((int256,int256))", sin); 
    success = address(BAddr).call(data);
    return success;
  }
}

The bool public success is introduced to capture the returned value of the call. When testing in Remix, this variable changes from false to true, after calling the TalkToA function. It means that the .call was successful.

The key of the solution is the line: bytes memory data = abi.encodeWithSignature("SetStruct((int256,int256))", sin);

2

Passing structs is a bad bad idea even between functions in the same contract. If you play around with structs a lot in your code, you may want to consider the effects of passing structs around and how you update them.

Contrary to what most people think, passing structs as variables affects your contract size limit as well, especially if you don't choose how you store and update the struct carefully. If your contracts are small you are unlikely to hit into this limit but on a big project, you will have a problem. How impactful? lets take a look.

Factors: my test contract where all this is happening is 18kb, just taking a snippet of code from it.

struct mysharesStruct {
        uint256 amount;
        uint256 amountETH;
        uint256 matchedETH;
        uint8 duration;
        uint256 expiry;
        address subscriber;
    }
mysharesStruct sharefile;//assign struct to var (declaring var type at same time)

function createShareLease(uint256 tokens, uint256 ethRequired, uint8 lease_days) external {

    sharefile = mysharesStruct(tokens, ethRequired, 0, lease_days, 0, msg.sender, 0);

}

Passing the struct to a variable as mysharesStruct sharefile; takes up a small amount of memory.

Depending on your coding approach (as i have expanded this into a discussion on the impact of passing structs in the first place) the problem begins when then inside the function you want to use your struct, if you then assign values to your struct using the approach above:

sharefile = mysharesStruct(tokens, ethRequired, 0, lease_days, 0, msg.sender, 0);

you will notice that Remix compiler then shows a contract size limit warning! That's a huge jump in memory consumed. This is because the struct is stored in memory when its being assigned values.

Its the equivalent of this code:

mysharesStruct[] public sharefile;//assign struct to var (declaring var type at same time) 

Used inside the functions as:

mysharesStruct memory structVar = mysharesStruct(tokens, ethRequired, 0, lease_days, 0, msg.sender, 0);

My recommendation to solidity developers working on big projects is to to not pass and update structs like above but instead, as below:

mapping(address => mysharesStruct) private _shareMapping;
struct mysharesStruct {
        uint256 amount;
        uint256 amountETH;
        uint256 matchedETH;
        uint8 duration;
        uint256 expiry;
        address subscriber;
    }

Place the struct in a mapping and then inside our function we assign values to it as;

function createShareLease(uint256 tokens, uint256 ethRequired, uint8 lease_days) external {
  _shareMapping[msg.sender].amount = tokens;
  _shareMapping[msg.sender].amountETH = ethRequired;
  _shareMapping[msg.sender].duration = lease_days;
  _shareMapping[msg.sender].subscriber = msg.sender;
}

You will notice as soon as you use that approach, the contract limit warning disappears. The difference here is that we are not passing our struct as a variable, but instead are choosing to store it in a mapping and then access and update it directly from there.

This is well worth your consideration if you want to avoid hitting contract size limits easily when playing around with structs in your code.

QYuQianchen answer of passing variables is the smarter way overall, big picture considered.

1
  • Amazing. Thank you so much for your answer.
    – Sami Awwad
    Commented Sep 27, 2023 at 4:08
1

As you did, to pass/return a struct you need the line pragma experimental ABIEncoderV2;. Remix itself warns you with

Warning: Experimental features are turned on. Do not use experimental features on live deployments.

What I suggest to you is to unwrap your struct when you need it as argument/return type, and use it only as internal storage.

For example, you could write your function in this way:

function SetStruct (int _x, int _y) public returns (int) {
  data = 10;
  Test.TStruct memory s;
  s.x = _x;
  s.y = _y;
  return s.x;
}

Doing so you can also remove pragma experimental ABIEncoderV2; line.

2
  • My application logic would benefit greatly if I could pass structs: I need to pass a lot of parameters. Unwrapping would lead to pretty ugly code and structs provide a clean solution to resolve this. Commented Jan 23, 2019 at 10:35
  • Obviously, like in most of all programming languages. But keep in mind that Solidity is still under development and maybe one day struct, or even functions, could be passed as argument / returned without any problems. But not being part of the community I don't know when it will be available.
    – 0Alic
    Commented Jan 23, 2019 at 11:04

Your Answer

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

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