Why can't I return dynamic array in Solidity? - solidity

I am trying to return a list of addresses by declaring an array and pushing to it in a for loop. However, Solidity doesn't like when I try to return a dynamic array.
function listMyPromises() public returns(uint256[] memory ){ //lists all my past promises
uint256[] memory List;
for(uint i=0; i<table.length; i++){
if(table[i].userAddress==msg.sender){
List.push(uint256(table[i].friendAddress));
}
}
return List;
}
Solidity is not allowing me to return a array stating that
TypeError: Data location must be "memory" or "calldata" for return parameter in function, but none was given.
However When I add memory in the return parameter I get the error:
TypeError: Member "push" is not available in uint256[] memory outside of storage
I have no clue what to do.

Solidity doesn't allow resizing (e.g. by using .push()) a memory array.
You need to calculate the size beforehand and then create a fixed-size memory array.
Source: docs
So the result is possibly going to look like this
function listMyPromises() public returns(uint256[] memory ){ //lists all my past promises
uint256 count = getCount();
uint256[] memory List = new uint256[](count);
uint256 j;
for (uint i = 0; i < table.length; i++) {
if(table[i].userAddress==msg.sender){
List[j] = uint256(table[i].friendAddress);
j++;
}
}
return List;
}
function getCount() internal view returns (uint) {
uint count;
for (uint i = 0; i < table.length; i++) {
if (table[i].userAddress == msg.sender) {
count++;
}
}
return count;
}
Depending on the datatypes wrapped in the table[i] struct, you might also run into another error. The friendAddress suggests that it's a type address, but it cannot be converted to uint256.

Related

ERC721A smart contract

I'm writing an ERC721A smart contract and getting a warning in the Remix IDE. There is the code:
function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) {
if (index >= balanceOf(owner)) revert OwnerIndexOutOfBounds();
uint256 numMintedSoFar = totalSupply();
uint256 tokenIdsIdx;
address currOwnershipAddr;
// Counter overflow is impossible as the loop breaks when uint256 i is equal to another uint256 numMintedSoFar.
unchecked {
for (uint256 i; i < numMintedSoFar; i++) {
TokenOwnership memory ownership = _ownerships[i];
if (ownership.addr != address(0)) {
currOwnershipAddr = ownership.addr;
}
if (currOwnershipAddr == owner) {
if (tokenIdsIdx == index) {
return i;
}
tokenIdsIdx++;
}
}
}
// Execution should never reach this point.
assert(false);
}
This code is from the Square Bears collection ( https://etherscan.io/address/0x2b1037def2aa4ed427627903bdef9bdd27ae1ea3#code ). I got it from a YouTube tutorial. I think the code works, but I keep getting a warning.
Warning: Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
--> contracts/ERC721A.sol:103:94:
|
103 | function tokenOfOwnerByIndex(address owner, uint256 index) public view override returns (uint256) {
| ^^^^^^^
I assume that I have to provide a named return value or variable, but the code seems to return an iterated value (i).
Because you've told the compiler that you will return a value from the function, but you didn't.
You should return a mock value even if you don't need to.
...
// Execution should never reach this point.
assert(false);
return tokenIdsIdx; // or simply return 0;
}

Try to return an array in a for loop

I am very new to Solidity, so sorry for the silly question. I am trying to add an element of a struct and then return it, here is my code:
struct Flat {
uint256 priceInWei;
address currentOccupant;
bool flatIsAvailable;
}
Flat[8] public flatDB;
modifier landlordOnly() {
require(msg.sender == landlordAddress);
_;
}
constructor() public {
landlordAddress = msg.sender;
for (uint i=0; i<8; i++) {
flatDB[i].flatIsAvailable = true;
if (i % 2 == 0) {
flatDB[i].priceInWei = 0.1 ether;
} else {
flatDB[i].priceInWei = 0.2 ether;
}
}
}
uint256[] array;
function getFlatDB() payable public returns (uint256) {
for (uint i=0; i<8; i++) {
array.push(flatDB[i].priceInWei);
}
return array;
}
But when I try to compile I have an error at line 41:
TypeError: Return argument type uint256[] storage ref is not implicitly convertible to expected type (type of first return variable) uint256. return array; ^---^
Can someone explain me what's wrong? Thanks!
Seems like the return type you have (uint256) doesn't correspond to the type you're actually trying to return (uint256[]). Try rewriting your getFlatDB function in the following way:
function getFlatDB() payable public returns (uint256[]) {
uint256[] memory array = new uint256[](8);
for (uint i=0; i<8; i++) {
array[i] = flatDB[i].priceInWei;
}
return array;
}
Note how we declare the temporary fixed size return array inside the function with memory keyword.

Solidity: Returns filtered array of structs without 'push'

I have this contract with an array of structs:
pragma solidity ^0.6.0;
pragma experimental ABIEncoderV2;
contract Tickets {
struct Ticket {
uint id;
int val;
}
Ticket[] tickets;
function addTicket(uint id, int val) public returns(bool success) {
Ticket memory newTicket;
newTicket.id = id;
newTicket.val = val;
tickets.push(newTicket);
return true;
}
function getTicket(uint id) public view returns(Ticket memory) {
uint index;
for(uint i = 0; i<tickets.length; i++){
if (tickets[i].id == id) {
index = i;
break;
}
}
Ticket memory t = tickets[index];
return t;
}
function findTickets(int val) public view returns(Ticket[] memory) {
Ticket[] memory result;
for(uint i = 0; i<tickets.length; i++){
if (tickets[i].val == val) {
result.push(tickets[i]); // HERE IS THE ERROR
}
}
return result;
}
}
I need to returns a filtered by val array but when I buil this code: result.push(tickets[i].id); it throw this error:
TypeError: Member "push" is not available in struct Tickets.Ticket memory[] memory outside of storage.
How I can implement the filter without using push ?
Returning dynamic-length array of struct is still a bit tricky in Solidity (even in the current 0.8 version). So I made a little workaround to make it work even in the 0.6.
Determine the result count, you'll need it for step 2.
Create a fixed-length array
Fill the fixed-length array
Return the fixed-length array
function findTickets(int val) public view returns(Ticket[] memory) {
uint256 resultCount;
for (uint i = 0; i < tickets.length; i++) {
if (tickets[i].val == val) {
resultCount++; // step 1 - determine the result count
}
}
Ticket[] memory result = new Ticket[](resultCount); // step 2 - create the fixed-length array
uint256 j;
for (uint i = 0; i < tickets.length; i++) {
if (tickets[i].val == val) {
result[j] = tickets[i]; // step 3 - fill the array
j++;
}
}
return result; // step 4 - return
}

If statement in for loop not filtering out items in solidity

// #param physicalAddress - the actual address of the home a host wants to list (not the ethereum address)
// #return _id - list of ids for homes
function listHomesByAddress(string _physicalAddress) public returns(uint [] _id ) {
uint [] results;
for(uint i = 0 ; i<homes.length; i++) {
if(keccak256(homes[i].physicalAddress) == keccak256(_physicalAddress) && homes[i].available == true) {
results.push(homes[i].id);
}
}
return results;
}
The result is supposed to be a list of ids which match the physical address entered however it does not filter through but returns all the available homes.
When I change to using String utils nothing changes.
Here is the whole code:
pragma solidity ^0.4.0;
import "browser/StringUtils.sol";
// #title HomeListing
contract HomeListing {
struct Home {
uint id;
string physicalAddress;
bool available;
}
Home[] public homes;
mapping (address => Home) hostToHome;
event HomeEvent(uint _id);
event Test(uint length);
constructor() {
}
// #param physicalAddress - the actual address of the home a host wants to list (not the ethereum address)
function addHome(string _physicalAddress) public {
uint _id = uint(keccak256(_physicalAddress, msg.sender));
homes.push(Home(_id, _physicalAddress, true));
}
// #param physicalAddress - the actual address of the home a host wants to list (not the ethereum address)
// #return _id - list of ids for homes
function listHomesByAddress(string _physicalAddress) public returns(uint [] _id ) {
uint [] results;
for(uint i = 0 ; i<homes.length; i++) {
string location = homes[i].physicalAddress;
if(StringUtils.equal(location,_physicalAddress )) {
results.push(homes[i].id);
}
}
return results;
}
}
The part giving you trouble is the line uint[] results;. Arrays declared as local variables reference storage memory by default. From the "What is the memory keyword" section of the Solidity docs:
There are defaults for the storage location depending on which type of variable it concerns:
state variables are always in storage
function arguments are in memory by default
local variables of struct, array or mapping type reference storage by default
local variables of value type (i.e. neither array, nor struct nor mapping) are stored in the stack
The result is you're referencing the first storage slot of your contract, which happens to be Home[] public homes. That's why you're getting the entire array back.
To fix the problem, you need to use a memory array. However, you have an additional problem in that you can't use dynamic memory arrays in Solidity. A workaround is to decide on a result size limit and declare your array statically.
Example (limited to 10 results):
function listHomesByAddress(string _physicalAddress) public view returns(uint[10]) {
uint [10] memory results;
uint j = 0;
for(uint i = 0 ; i<homes.length && j < 10; i++) {
if(keccak256(homes[i].physicalAddress) == keccak256(_physicalAddress) && homes[i].available == true) {
results[j++] = homes[i].id;
}
}
return results;
}

Add element to struct

I can't use ( push ) because it is used with state variable only
This is the error :
Error message
Is there any alternatives for ( push )
contract m{
struct Message{
address sender;
address receiver;
uint msgContent;
} // end struct
Message[] all;
function get ( address from ) internal
returns ( Message[] subMsgs){
for ( uint i=0; i<all.length ; i++)
{
if ( all[i].sender == from )
{
subMsgs.push (all[i]);
}
}
return subMsgs;
}
} // end contract
You can only use push on dynamic-size arrays (i.e. storage arrays), and not fixed-size arrays (i.e. memory arrays) (see Solidity array documentation for more info).
So to achieve what you want, you'll need to create a memory array with a desired size and assign each element one by one. Here is the example code:
contract m {
struct Message{
address sender;
address receiver;
uint msgContent;
} // end struct
Message[] all;
function get(address from) internal returns (Message[]) {
// Note: this might create an array that has more elements than needed
// if you want to optimize this, you'll need to loop through the `all` array
// to find out how many elements from the sender, and then construct the subMsg
// with the correct size
Message[] memory subMsgs = new Message[](all.length);
uint count = 0;
for (uint i=0; i<all.length ; i++)
{
if (all[i].sender == from)
{
subMsgs[count] = all[i];
count++;
}
}
return subMsgs;
}
} // end contract