23

I know that it's possible to return a struct through argument destructing:

contract Test {
    struct Point {
        int x;
        int y;
    }

    Point point;

    function getPoint() returns (int x, int y) {
        x = point.x;
        y = point.y;
    }
}

The problem is that I need to return an array of structs:

contract Test {
    struct Point {
        int x;
        int y;
    }

    Point[] allPoints;

    function getPoints() returns (???) {
        ???
    }
}

Is it possible?

Henrique Barcelos
  • 2,481
  • 4
  • 20
  • 38

7 Answers7

13

From what I've tested and read, you cannot return dynamic arrays from Solidity functions yet, but the developers are planning to include this functionality sometime. References cryptic type error for multiple return values #164 and Return an array from a Solidity function.

In the meantime, you could create a function getNumberOfPoints() to return the number of points, and modify getPoint() to take an index and return the (x, y) of the point in the array pointed to by the index.

BokkyPooBah
  • 40,274
  • 14
  • 123
  • 193
12

This can be done using the v2 ABI encoder. Let's talk about how to use this encoder, and then how to return an array of structs.

How to use abicoderv2

source: Solidity docs

abicoderv2 is the ABI encoder by default starting in Soldity 0.8. It was originally considered experimental, but as of Solidity 0.6 was given non-experimental status. As such, you're using it by default in 0.8 and up.

To use it from 0.6 to 0.8 (non-inlusive), put the following at the top of the contract (I've generally seen it put directly beneath the pragma declaring the version of Solidity):

pragma abicoderv2;

For Solidity <0.6, use:

pragma experimental ABIEncoderV2

How to return an array of structs

Example code:

struct myStruct {
  address foo;
  uint256 bar;
}
myStruct[] private myStructs;

function getMyStruct() public view returns(myStruct[] memory) { return myStruct; }

The memory was necessary if there was an interface to the contract implementing a skeleton of this function, it may be able to be taken out if there isn't an interface behind it, though I'm not sure you'd want to even if you could. If I get more clarity on this last point, I'll try to update the answer.

The Renaissance
  • 2,749
  • 1
  • 17
  • 45
  • 5
    Nice that it took only 5 years to get there :´D – Henrique Barcelos Apr 22 '21 at 21:54
  • Thanks for noticing the answer! I was kinda worried it would sit at the bottom of the pile for a while. – The Renaissance Apr 23 '21 at 05:39
  • 2
    To be honest, I was aware of this already, but I had completely forgot I ever asked this question. The upvote and the accept are for your necromancer skills :v – Henrique Barcelos Apr 26 '21 at 18:19
  • Thanks - I was surprised it wasn't updated yet (the experimental option has been around for years), I figured this would be a public service to anyone searching it now - this question is usually the first result – The Renaissance Apr 27 '21 at 10:48
  • 1
    for 0.8.7, you have to put abicoder v2 instead of abicoderv2 – Kaneda Oct 14 '21 at 23:29
  • @Kaneda I don't think it should be necessary to select the v2 encoder in >=0.8, it's the default there according to the docs (link above) - if there's something I'm missing, please point it out. Thanks! – The Renaissance Oct 18 '21 at 13:04
  • 1
    @TheRenaissance weird, I was trying to return struct in 0.8.7 but it didn't work without abicoder v2. Maybe I'm mistaken, anyways if docs say it's built-in then that's the case. – Kaneda Oct 18 '21 at 18:14
  • hmmmmmmmmm, yeah the docs say you shouldn't need it, but there's no arguing with reality if you actually did. The first thing I can think of is that maybe you're using a range like pragma solidity >-0.7.0 <0.9.0; and think you're getting 0.8.7, but are really getting something in the 0.7 range, but I think that's unlikely. If you get to the bottom of this, please let me know! – The Renaissance Oct 19 '21 at 08:22
  • @Kaneda You definitely don't need the pragma in Solidity 0.8.14 (I don't know why you found you needed it in 0.8.7). – Luke Hutchison Jun 10 '22 at 07:48
2

Given certain conditions on how you've implemented your data structure it is possible to do so. For example look at the code below for contract and javascript:

contract Y{
   string head;
   struct Temp{
      address addr;
      string next;
      string current;
   }
mapping (string => Temp) _temp;
function Y(){
    _temp['root'].addr = 0;
    _temp['root'].next = 'root';
    _temp['root'].current = 'root';
    head = 'root';
}
function addNodes(string _current, address _addr){
    string memory _curr = _current;
    _temp[_current].current = _curr;
    _temp[_current].next = head;
    _temp[_current].addr = _addr;
    head = _curr;
}

function getHead() constant returns(string){
    return head;
}

function getNodes(string _current) constant returns (string,string,address){
    string temp1 = _temp[_current].next;
    address _addr = _temp[_current].addr;
    string temp2 = _temp[_current].current;
    return (temp1,temp2,_addr);
}

}

getList.js

 var Head = y.getHead();
 var tempCurrent="";
 var tempAddress="";
 var next="";
 var arr="";
 var length="";
 var temp="";
 var t_Current="";
 var t_Address="";
 for(;next!='root';){
   temp = y.getNodes(Head);
   length = temp.lenght;
   temp = temp +  "";
   arr = temp.split(",");
   next = arr[0];
   tempCurrent = arr[1];
   tempAddress = arr[2];
   Head = next;
   t_Current = t_Current + tempCurrent + ";";
   t_Address = t_Address + tempAddress + ";";
}
var arr_Current = t_Current.split(";");
console.log(arr_Current);
//console.log(t_Current);

Once you have deployed contract and have added data to contract you can simply call getList.js using loadScript("getList.js") from geth and bingo you will have data from all nodes in list .

PS: This is just a demo you can do much more with your contract.

2

Here is the way I came up with to return dynamic local arrays from a method. Am sure using this, one can come up with various ways to get data using arrays.

Solidity Contract Method

function getArrayData() constant returns (bytes32[] _data1, bytes32[] _data2) {

    bytes32[] memory arrData1 = new bytes32[](5);
    bytes32[] memory arrData2 = new bytes32[](5);

    arrData1[0]='Data 1 - 1';
    arrData1[1]='Data 1 - 2';
    arrData1[2]='Data 1 - 3';
    arrData1[3]='Data 1 - 4';
    arrData1[4]='Data 1 - 5';

    arrData2[0]='Data 2 - 1';
    arrData2[1]='Data 2 - 2';
    arrData2[2]='Data 2 - 3';
    arrData2[3]='Data 2 - 4';
    arrData2[4]='Data 2 - 5';

    return (arrData1, arrData2);
 }

NodeJS Call

contractInstance.getArrayData.call((err, res) =>{
      console.log('Data 1: ' + web3.toAscii(res[0][0])); 
      console.log('Data 1: ' + web3.toAscii(res[0][1])); 
      console.log('Data 1: ' + web3.toAscii(res[0][2])); 
      console.log('Data 1: ' + web3.toAscii(res[0][3])); 
      console.log('Data 1: ' + web3.toAscii(res[0][4])); 

      console.log('Data 2: ' + web3.toAscii(res[1][0])); 
      console.log('Data 2: ' + web3.toAscii(res[1][1])); 
      console.log('Data 2: ' + web3.toAscii(res[1][2])); 
      console.log('Data 2: ' + web3.toAscii(res[1][3])); 
      console.log('Data 2: ' + web3.toAscii(res[1][4])); 
    });

Hope this helps!

Susmit
  • 1,804
  • 2
  • 14
  • 29
2

Simply keep track of the indicies, for example by address

Struct[] public objects;
mapping(address => uint256[]) private indicies;
function getIndicies() constant returns (uint256[]) {
  return indicies[msg.sender];
}

Then you can use the following (written as test):

it('should return objects', async () => {
  const indicies = await contract.getIndicies.call({ from: address });
  for (let i in indicies) {
    const index = indicies[i].toNumber();
    //call public abi function of objects
    const object = await contract.objects.call(index);
    console.log(object);
  }
  assert(true, 'balance should be zero');
});
mattdlockyer
  • 123
  • 5
  • It will require a trade off, since it will require an state variable which willl cost more gas, also your function to return should be view type to not behave like a transaction – Eduardo Pereira Feb 11 '18 at 21:02
1

With Solidity 0.4.18, I was able to return a dynamic array of structs from a function but wasn't able to deal with it when I called this function from another contract.

ulu
  • 740
  • 6
  • 20
  • can you please share an example? – Ajay Jadhav Jan 20 '18 at 12:20
  • struct Qryp{ address sender; string senderName; uint created; string message; }

    Qryp[] qryps;

    function Qrypper() public { qryps.push(Qryp(0, "ulu", now, "hi")); }

    function getQryps() public constant returns (Qryp[]){ return qryps; }

    – ulu Jan 23 '18 at 10:06
  • 1
    Showing TypeError: This type is only supported in the new experimental ABI encoder. Use "pragma experimental ABIEncoderV2;" to enable the feature. Using pragma version greater than 0.4.18 – Amit Sharma Jun 18 '18 at 02:08
  • Yes, I used this pragma header. – ulu Jun 19 '18 at 19:42
1

Return an array of struct from a function

pragma solidity ^0.5.0;
pragma experimental ABIEncoderV2;
contract Money {
  struct People{
    uint id;
    string name;
    uint amount;
  }
  mapping (uint => People) public peoples;
  event votedEvent(uint indexed _candidateId);
  uint public candidateConut;

  constructor() public {
    candidateConut = 0;
    addCandidate("Holder 1");
    addCandidate("Holder 2");
  }
  function addCandidate(string memory _name) public {
    peoples[candidateConut] = People(candidateConut,_name,0);
    candidateConut++;
  }
  //return Single structure
  function get(uint _candidateId) public view returns(People memory) {
    return peoples[_candidateId];
  }
  //return Array of structure Value
  function getPeople() public view returns (uint[] memory, string[] memory,uint[] memory){
      uint[]    memory id = new uint[](candidateConut);
      string[]  memory name = new string[](candidateConut);
      uint[]    memory amount = new uint[](candidateConut);
      for (uint i = 0; i < candidateConut; i++) {
          People storage people = peoples[i];
          id[i] = people.id;
          name[i] = people.name;
          amount[i] = people.amount;
      }

      return (id, name,amount);

  }
  //return Array of structure
  function getPeoples() public view returns (People[] memory){
      People[]    memory id = new People[](candidateConut);
      for (uint i = 0; i < candidateConut; i++) {
          People storage people = peoples[i];
          id[i] = people;
      }
      return id;
  }
}