1

This question was asked here but I thought I would make it a little more clear. Is it possible to access the funders mapping (bolded) via Truffle console in the code below ? This is taken from the Solidity docs:

struct Funder {
    address addr;
    uint amount;
}

struct Campaign {
    address beneficiary;
    uint fundingGoal;
    uint numFunders;
    uint amount;
    mapping (uint => Funder) funders;
}

uint numCampaigns;
mapping (uint => Campaign) campaigns;

function newCampaign(address beneficiary, uint goal) returns (uint campaignID) {
    campaignID = numCampaigns++; // campaignID is return variable
    // Creates new struct and saves in storage. We leave out the mapping type.
    campaigns[campaignID] = Campaign(beneficiary, goal, 0, 0);
}

function contribute(uint campaignID) payable {
    Campaign storage c = campaigns[campaignID];
    // Creates a new temporary memory struct, initialised with the given values
    // and copies it over to storage.
    // Note that you can also use Funder(msg.sender, msg.value) to initialise.
    c.funders[c.numFunders++] = Funder({addr: msg.sender, amount: msg.value});
    c.amount += msg.value;
}

After you run newCampaign and contribute to add funds to the newly created campaign, the object returned back is below:

[ '0x740bd112e4d310a69aae3cee9ca8e72d1b1f8a21', { [String: '15000'] s: 1, e: 4, c: [ 15000 ] }, { [String: '5'] s: 1, e: 0, c: [ 5 ] }, { [String: '16000'] s: 1, e: 4, c: [ 16000 ] }, { [String: '1504125631'] s: 1, e: 9, c: [ 1504125631 ] } ]

All the variables in the Campaign struct are visible except for the funders variable. Is this because funders is not initialized in storage perhaps?

2 Answers 2

1

If you can pardon the self-promotion, this explainer might reveal a way to accomplish the goal: https://medium.com/@robhitchens/enforcing-referential-integrity-in-ethereum-smart-contracts-a9ab1427ff42

EDIT

The main idea is as follows.

The basic data structure for both entities is a mapping of structs (for random access) and an unordered index of keys as a dynamic array (to facilitate a count and enumeration). There's an example under the heading "Mapped Struct with Index" over here. Are there well-solved and simple storage patterns for Solidity?

So basically, you just build the contract with two of those for your two entities.

Next, the join. The suggestion is you embed a dynamic array of keys where you're using a mapping. That will make it possible to count the rows in the list and also to iterate over the list by returning the keys one at a time.

Roughly:

struct CampaignStruct {
  ...
  address funders[];
  bool exists;
}

mapping(bytes32 => CampaignStruct) public campaignStructs;
bytes32[] public campaignIndex;

struct FunderStruct {
  ...
  bool exists;
}

mapping(address => FunderStruct) public funderStructs;
address[] public funderIndex;

function getCampaignCount() public constant returns(uint count) {
   return campaignIndex.length;
}

function getFunderCount() public constant returns(uint count) {
   return funderIndex.length;
}

function getCampaignAtIndex(uint row) public constant returns(<details>) {}

function getCampaignFunderCount(bytes32 campaignId) public constant returns(uint count) {
   return campaignStructs[campaignId].funders.length;
}

function getCampaignFunderAtIndex(bytes32 campaignId, uint row) public constant returns(address funder)

{
   return campaignStructs[campaignId].funders[row];
}

If you don't need delete, an upsert style writer will do the trick because you can write directly to the mapping. New keys are added to the indexes with .push(). You can use a bool exists to know if you're updating or inserting.

Hope it helps.

5
  • An answer like this is not very helpful if the links dissapear it will end without content. It is better if you put here the main idea and the links works as a complement.
    – Ismael
    Commented Aug 31, 2017 at 2:24
  • 1
    Point taken. I've added a very compressed that is hopefully coherent. Commented Aug 31, 2017 at 2:58
  • @RobHitchens I'm still confused as to how to access the mapping within a struct given the code posted in my question. Edmund suggested building your own accessor function.
    – atc
    Commented Aug 31, 2017 at 3:02
  • The thing is you can't. This is why I suggest dropping the mapping that you can't access and using an array that you can access. For clarity (I hope), you could build a function that returns one mapped value at a time, but you would have to supply the key to the mapping to know which "one" to return. That means you'll need to keep a list somewhere. The whole idea of the code sketch above is to keep a list instead of using a data structure that won't do what you want. Commented Aug 31, 2017 at 3:51
  • Gotcha point taken. I'm just trying to work within the context of the Solidity docs.
    – atc
    Commented Aug 31, 2017 at 21:36
0

You can't retrieve a list of key->value pairs of a mapping inside the struct for the same reason that you can't retrieve the key-value pairs of a mapping outside a struct: The contract doesn't know what they are. See this answer for a more detailed explanation: Can we get all elements stored in a mapping in the contract

Assuming you have the keys yourself, you can make your own accessor to retrieve it, eg something like:

function campaign_funder(uint campaignID, uint funderID) constant returns (address) {
    return campaigns[campaignID].funders[funderID].addr;
}

If you need to be able to retrieve a list of the funders without already knowing their IDs, you'll have to store some extra data so the contract can get at the IDs. See the answer I linked for details.

5
  • You can't retrieve a list of key->value pairs of a mapping inside the struct for the same reason that you can't retrieve the key-value pairs of a mapping outside a struct. I don't believe this is true as per the docs. If you make the campaigns type to public so that it reads mapping (uint => Campaign) public campaigns; you can access the mapping by simply doing campaigns[0] to get the first key->value pair. That is how I was able to get the output in the question. The accessor function seems to be the way to go.
    – atc
    Commented Aug 31, 2017 at 2:55
  • I also tried compiling the campaign_funder function you defined and it gives me the error "Funder storage ref is not implicitly convertible to expected type (type of first return variable) address." The function is trying to return a struct which is not possible in Solidity.
    – atc
    Commented Aug 31, 2017 at 3:20
  • Sorry, yes, the funders in campaigns[campaignID].funders are Funder structs not addresses, so (also untested) return campaigns[campaignID].funders[funderID].addr; Commented Aug 31, 2017 at 6:51
  • "you can access the mapping by simply doing campaigns[0] to get the first key->value pair.": That works because you're supplying the mapping key, which is 0. If you want to say "just give me all the key/value pairs of this mapping" then Solidity won't be able to help you. You have to either pass the key into an accessor function or store it (or the information you need to know what it is, eg the last index of a list that you assign sequentially) elsewhere in your contract. Commented Aug 31, 2017 at 6:57
  • Yup this make sense.
    – atc
    Commented Aug 31, 2017 at 22:27

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.