How to return a struct inside a nested mapping in solidity - solidity

I'm working on a smart contract that allows users to subscribe to different Plans.
I have a nested mapping that returns a struct(Subscription). I wanted to create a function that returns all the subscriptions that exist in a specific plan(0,1,2..).
The function that I wrote doesn't return all structs it returns only the first one after that it returns only zeros.
totalSubscriptions increase when a new user creates a new subscription.
address[] allSubcribers contains all the addresses of subscribers.
currentId represents the id of the plan that a subscriber signed up for.
struct Subscription {
address subscriber;
uint start;
uint nextPayment;
}
mapping(address => mapping(uint => Subscription)) public subscriptions;
function getAllsubscriptions() external view returns (Subscription[] memory) {
Subscription[] memory items = new Subscription[](totalLoans);
for(uint256 i = 0; i < totalSubscriptions; i++) {
uint256 currentId = i;
LoanRequest storage currentItem = subscriptions[allSubcribers[currentId]][currentId];
items[currentId] = currentItem;
currentId += 1;
}
return items;
}
What do you think?

Related

Member "push" is not available in struct ... memory outside of storage

Below is the contract:
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.6.12;
// Contracts
pragma experimental ABIEncoderV2;
contract test{
mapping(address => address) public managers;
struct tokenInfo{
address token;
uint256 decimals;
uint256 amount;
}
struct recordInfo{
uint time;
tokenInfo[] tokens;
}
mapping(address => mapping(uint => recordInfo)) public record_List;
function claim(address pool) public {
address manager = address(0);
require(managers[pool] != manager,"claim is not manager"); // this `require` is necessary, if delete it, cannot compile, why?
//record
recordInfo storage record; // claim `storage`, compile succeed
delete record.time;
delete record.tokens;
record.time = block.timestamp;
for( uint i = 0;i < 2;i++){
uint balance;
tokenInfo memory token;
token.token = address(0);
token.amount = balance;
token.decimals = 18;
record.tokens.push(token);
}
record_List[pool][2] = record;
}
}
then I change the recordInfo storage record to recordInfo memory record;, the contract cannot compile in the remix
function claim(address pool) public {
address manager = address(0);
require(managers[pool] != manager,"claim is not manager"); // this `require` is necessary, if delete it, cannot compile, why?
//record
recordInfo memory record; // claim `memory`, cannot compile
delete record.time;
delete record.tokens;
record.time = block.timestamp;
for( uint i = 0;i < 2;i++){
uint balance;
tokenInfo memory token;
token.token = address(0);
token.amount = balance;
token.decimals = 18;
record.tokens.push(token);
}
record_List[pool][2] = record;
}
and here is the error msg :
from solidity:
tests/testuint.sol:38:13: TypeError: Member "push" is not available in struct test.tokenInfo memory[] memory outside of storage.
record.tokens.push(token);
^----------------^
Why claimed storage here okay instead of memory?
can here claim it to memory?
If storage is used, why need the require?
Why claimed storage here okay instead of memory?
This happened because push() method can use only for object memorized in storage. Thus, for object stored in memory you can only insert data specifying index.
More information about this topic on documentation.
Can here claim it to memory?
No, you cannot claim it changing storage position from storage to memory, because of all records are stored into storage space and you cannot update or delete their data if you change memory position.
If storage is used, why need the require?
I think that it's a bug about compiler 0.6. Thus, if you try to change Solidity compiler from 0.6 to 0.8, the issues will show itself. Irrespective of this, the compiler say you that the record (declared into claim() method) don't point to any data present into record_List mapping. In a simple words, it wasn't initialized. In your case, you can consider to retrieve a specific element from record_List mapping and then you do delete and update operations.
An example:
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.6.12;
// Contracts
pragma experimental ABIEncoderV2;
contract test{
mapping(address => address) public managers;
struct tokenInfo{
address token;
uint256 decimals;
uint256 amount;
}
struct recordInfo{
uint time;
tokenInfo[] tokens;
}
mapping(address => mapping(uint => recordInfo)) public record_List;
function claim(address pool, uint idRecord) public {
address manager = address(0);
require(managers[pool] != manager,"claim is not manager");
// NOTE: I retrieve a specific element from record_List mapping using pool and idRecord parameters for querying the mapping
recordInfo storage record = record_List[pool][idRecord];
delete record.time;
delete record.tokens;
record.time = block.timestamp;
for( uint i = 0;i < 2;i++){
uint balance;
tokenInfo memory token;
token.token = address(0);
token.amount = balance;
token.decimals = 18;
record.tokens.push(token);
}
record_List[pool][2] = record;
}
}

Append an item into an array inside a struct

Working on solidity 8.12, I have two structs, namely singer and event, two arrays one for singers and another for events. I want to write down a function that creates an event, specifying the singer id, and automatically append the event id into the array of events attended by the singer:
struct Singer {
uint id;
uint[] events_ids; // THIS IS WHAT I WANT TO UPDATE WITH THE NEW EVENT ID
}
struct Event {
uint id;
uint singer_id;
}
singer[] public SingerList;
event[] public EventList;
uint public n_singers;
uint public n_events;
function CreateEvent(uint singer_id) external {
if (singer_id > n_singers) { // singer id greater than the number of current singers.
revert SingerIdError(singer_id);
}
else if (singer_id == n_singers){ // the singer does not exist yet! Create it!
n_singers ++;
--------> SingerList.push(Singer(singer_id))); // HOW TO APPEND THE EVENT ID TO THE LIST OF EVENTS IN SINGER STRUCT?
EventList.push(Event(n_events, singer_id));
}
else { // the pilot already exists
Singer storage _singer = SingerList[singer_id];
_singer.event_ids.push(n_events);
EventList.push(Event(n_events, n_events));
}
n_events ++;
}```
Using 'mapping' instead of array-of-struct, gives you better control over your state variables with no extra hassles to control the array indexes. It also costs lower gas fee.
mapping(uint => uint[]) Singer;
mapping(uint => uint[]) Event;
uint public n_singers;
uint public n_events;
function CreateEvent(uint singer_id) external {
if(singer_id > n_singers) {
revert SingerIdError(singer_id);
}
else if (singer_id == n_singers) {
n_singers ++;
Singer[singer_id].push(n_events); // HOW TO APPEND THE EVENT ID TO THE LIS....
Event[n_events].push(singer_id);
} else {
Singer[singer_id].push(n_events);
}
n_events++;
}`

How to store unique value in an array using for loop in Solidity?

I want to store unique addresses in the array and then increment the count. But I am not able to do it. The count variable is not incremented and returns 0. Also redundant addresses are stored in the array.
Can you please help?
function placeBid() public payable notOwner afterStart beforeEnd returns(bool){
require(auctionState == State.Running);
uint currentBid = 0;
if(totalBidder>0)
{
currentBid = bids[msg.sender] + msg.value;
require(currentBid > highestBindingBid);
}
else{
require(msg.value > startBid && msg.value > highestBindingBid);
currentBid = msg.value;
}
bids[msg.sender] = currentBid;
for(uint i=0; i<bidders.length; i++){
if(msg.sender!=bidders[i])
bidders.push(payable(msg.sender));
totalBidder++;
}
}
highestBindingBid = currentBid;
highestBidder = payable(msg.sender);
return true;
}
An easy (and cheaper compared to your snippet) way to push only unique items to a storage array is to duplicate the values in a mapping, and then validate against the mapping.
pragma solidity ^0.8;
contract MyContract {
address payable[] public bidders;
mapping (address => bool) isBidder; // default `false`
// only add `msg.sender` to `bidders` if it's not there yet
function placeBid() public {
// check against the mapping
if (isBidder[msg.sender] == false) {
// push the unique item to the array
bidders.push(payable(msg.sender));
// don't forget to set the mapping value as well
isBidder[msg.sender] = true;
}
}
}

Solidity ParseError: Expected primary expression

When I am compiling with truffle it is giving me this error
I got this error after adding payable
prior to that it was
winners[j].transfer(betwa*(10000+(LoserBet*10000/WinnerBet))/10000);
I had to add it because I was getting another error
Which was
TypeError: "send" and "transfer" are only available for objects of type "address payable", not "address".
My Complete Contract :
pragma solidity ^0.5.16;
contract Betting {
address public owner;
uint256 public minimumBet;
uint256 public totalBetsOne;
uint256 public totalBetsTwo;
address[] public players;
struct Player {
uint256 amountBet;
uint16 teamSelected;
}
// The address of the player and => the user info
mapping(address => Player) public playerInfo;
function() external payable {}
constructor() public {
owner = msg.sender;
minimumBet = 100000000000000;
}
function kill() public {
if(msg.sender == owner) selfdestruct(msg.sender);
}
function checkPlayerExists(address player) public view returns(bool){
for(uint256 i = 0; i < players.length; i++){
if(players[i] == player) return true;
}
return false;
}
function bet(uint8 _teamSelected) public payable {
//The first require is used to check if the player already exist
require(!checkPlayerExists(msg.sender));
//The second one is used to see if the value sended by the player is
//Higher than the minimum value
require(msg.value >= minimumBet);
//We set the player informations : amount of the bet and selected team
playerInfo[msg.sender].amountBet = msg.value;
playerInfo[msg.sender].teamSelected = _teamSelected;
//then we add the address of the player to the players array
players.push(msg.sender);
//at the end, we increment the stakes of the team selected with the player bet
if ( _teamSelected == 1){
totalBetsOne += msg.value;
}
else{
totalBetsTwo += msg.value;
}
}
// Generates a number between 1 and 10 that will be the winner
function distributePrizes(uint16 teamWinner) public {
address[1000] memory winners;
//We have to create a temporary in memory array with fixed size
//Let's choose 1000
uint256 count = 0; // This is the count for the array of winners
uint256 LoserBet = 0; //This will take the value of all losers bet
uint256 WinnerBet = 0; //This will take the value of all winners bet
address playerAddress;
//We loop through the player array to check who selected the winner team
for(uint256 i = 0; i < players.length; i++){
playerAddress = players[i];
//If the player selected the winner team
//We add his address to the winners array
if(playerInfo[playerAddress].teamSelected == teamWinner){
winners[count] = playerAddress;
count++;
}
}
//We define which bet sum is the Loser one and which one is the winner
if ( teamWinner == 1){
LoserBet = totalBetsTwo;
WinnerBet = totalBetsOne;
}
else{
LoserBet = totalBetsOne;
WinnerBet = totalBetsTwo;
}
//We loop through the array of winners, to give ethers to the winners
for(uint256 j = 0; j < count; j++){
// Check that the address in this fixed array is not empty
if(winners[j] != address(0)){
address add = winners[j];
uint256 betwa = playerInfo[add].amountBet;
//Transfer the money to the user
payable(winners[j]).transfer( (betwa*(10000+(LoserBet*10000/WinnerBet)))/10000 );
}
}
delete playerInfo[playerAddress]; // Delete all the players
players.length = 0; // Delete all the players array
LoserBet = 0; //reinitialize the bets
WinnerBet = 0;
totalBetsOne = 0;
totalBetsTwo = 0;
}
function AmountOne() public view returns(uint256){
return totalBetsOne;
}
function AmountTwo() public view returns(uint256){
return totalBetsTwo;
}
}
What I have tried is making the address payable in this function but It is not working I have tried to replace memory with payable but still it isn't working
My versions
Truffle v5.4.18 (core: 5.4.18)
Solidity v0.5.16 (solc-js)
Node v14.15.1
Web3.js v1.5.3
The payable() conversion from address to address payable was introduced in Solidity 0.6.
Source: https://docs.soliditylang.org/en/latest/060-breaking-changes.html#new-features
Since your contract is using (deprecated) version 0.5.16, it doesn't allow this conversion. So you need to define the winners array already as address payable:
// added `payable`
address payable[1000] memory winners;
Then you'll be able to use the .transfer() method of the address payable type:
// `winners[j]` is payable
winners[j].transfer(...);

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;
}