Getting structs and mappings from contract storage

AronVanAmmersAronVanAmmers Amsterdam, NetherlandsMember Posts: 40 ✭✭
edited March 2015 in Solidity
Hi guys, going through the Solidity tutorials by @KenK and I like what I've seen up to now. Structured contract programming and easy calls from Javascript in the DApp.

A question with regard to that: is there an easy way to get the data stored in structs and mappings through the Javascript API?

For example the first contract in this chapter with one function added:
contract Test{
	struct coinWallet {
	uint redCoin;
	uint greenCoin;
	}
	coinWallet myWallet;
	function Test(){
		myWallet.redCoin = 500;
		myWallet.greenCoin = 250;
	}
        
        function GetRed() returns (uint redAmount){
		return myWallet.redCoin;
	}
}
Using var MyContract = web3.eth.contract(...) with the ABI allows calling the functions of contracts easily: in this case I can callmyContract.GetRed().

What I'm looking for is a way to do something like this:
var cw = myContract.myWallet;
var total = cw.redCoin + cw.greenCoin;
Ideally also working with mappings. My thinking is that this would be possible without a transaction, as it's only accessing the contract's storage, which is public.

Is there a way to do this? If not, is it planned?

Comments

  • mids106mids106 Member Posts: 188 ✭✭✭
    You can make a GetTotal() function which returns the total amount. This function you can then `call` from Javascript (no transaction cost for call, since it doesn't have any side effects) and you are done.

    Alternatively you can use the accessors that are automatically generated for public variables: https://github.com/ethereum/wiki/wiki/Solidity-Features#state-variable-accessors
    Getters for struct types (or mappings to struct types) just return multiple values (all except mappings).
  • AronVanAmmersAronVanAmmers Amsterdam, NetherlandsMember Posts: 40 ✭✭
    Thanks! Got it working with the public struct, and a GetTotal() function. Example contract for future reference below:
    contract Test{
    	struct coinWallet {
    		uint redCoin;
    		uint greenCoin;
    		string32 userName;
    	}
    
    	coinWallet public myWallet;
    	mapping (address => coinWallet) public allWallets;
    
    	function Test(){
    		myWallet.redCoin = 500;
    		myWallet.greenCoin = 250;
    		myWallet.userName = "me";
    
    		allWallets[tx.origin] = myWallet;
    	}
            
    	function GetRed() returns (uint redAmount){
    		return myWallet.redCoin;
    	}
    
    	function GetTotal() returns (uint totalAmount){
    		return myWallet.redCoin + myWallet.greenCoin;
    	}
    }
    Apparently accessing a public mapping isn't supported yet as calling it from Javascript in AlethZero results in:

    TypeError: undefined is not an object (evaluating 'value.indexOf')

    But that's something that could be worked around.
  • cslarsoncslarson Member Posts: 11
    edited March 2015
    @AronVanAmmers to access mapped items in my contracts i've created a function for retrieving each individual mapped attribute by passing in the mapping id (in your case tx.origin). though in your contract i don't see how additional wallets are added to allWallets, but i'm probably missing something!
    the checkGoalReached function in the crowdfunding example might be helpful?
  • AronVanAmmersAronVanAmmers Amsterdam, NetherlandsMember Posts: 40 ✭✭
    @cslarson yeah that's the solution I was thinking of. It could be implemented in Ethereum itself so contracts can stay as simple as possible, but this is definitely a workable solution. This contract is just a dummy to test with how public structs and mappings were handled, it does nothing useful ;).

    Thanks!
  • chrisethchriseth Member Posts: 170 ✭✭✭
    Accessor functions for mappings to structs should just work. Could you please tell how you called the function? The signature for the accessor for allWallets in your latest example should be
    function allWallets(address) returns (uint, uint, string)
  • cslarsoncslarson Member Posts: 11
    @AronVanAmmers yes that would be great! maybe it's in the works...
  • AtomrigsAtomrigs Member Posts: 23
    edited March 2015
    Your code stores the myWallet instance twice, one in the contract's root space and one in the allWallets collection.
    I modified your code a little bit.
    I deleted "coinWallet public myWallet;"
    and add "coinWallet myWallet = allWallets[tx.origin];" inside the Test() function.
    
    contract Test{
    	struct coinWallet {
    		uint redCoin;
    		uint greenCoin;
    		string32 userName;
    	}
    	mapping (address => coinWallet) public allWallets;
    	function Test(){
                    coinWallet myWallet = allWallets[tx.origin]; // create a coinWallet instance 
    		myWallet.redCoin = 500;
    		myWallet.greenCoin = 250;
    		myWallet.userName = "me";
    	}
    }
    Now we can access the my wallet data this way;

    >>> var Test = web3.eth.contractFromAbi([{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"allWallets","outputs":[{"name":"redCoin","type":"uint256"},{"name":"greenCoin","type":"uint256"},{"name":"userName","type":"string32"}],"type":"function"}]); >>> var contractAddr = "0x....."; >>> var myAddr = "0x....."; >>> var test = Test(contractAddr); >>> var myWallet = test.allWallets[myAddr]; >>> myWallet.length; 3 >>>myWallet[2]; "me" >>>myWallet[0].toNumber() + myWallet[1].toNumber(); 750
  • AtomrigsAtomrigs Member Posts: 23
    There was a mistake on the js code above.
    var myWallet = test.allWallets[myAddr];

    should be

    var myWallet = test.allWallets(myAddr);
  • AronVanAmmersAronVanAmmers Amsterdam, NetherlandsMember Posts: 40 ✭✭
    Thanks @Atomrigs, that's what I was looking for. Those lines work in AlethZero PoC8 master branch, and they allow accessing mappings easily.

    The only thing left to wish for is calling properties of the struct in the mapping by their name instead of their index [0], [1], (..).

    E.g. instead of this:
    var total = myWallet[0].toNumber() + myWallet[1].toNumber()
    
    I would like to do something like this:
    var total = myWallet.redCoin.toNumber() + myWallet.greenCoin.toNumber()
    
    But accessing the mapping contents like that without having to add additional functions to the contract is already a step forward.
  • AronVanAmmersAronVanAmmers Amsterdam, NetherlandsMember Posts: 40 ✭✭
    And for completeness @chriseth, I erroneously called the mapping like this from Javascript.
    t.call().allWallets()
    
    Where it should have been:
    t.call().allWallets("0x....")
    
    I didn't know the key of the item had to be passed to the accessor function. Sorry for leaving that out of my earlier post.

    @KenK eagerly awaiting the next JS part of your tutorials where this will be explained :) (even though I already have my answer).
  • cslarsoncslarson Member Posts: 11
    cheers @Atomrigs. I hadn't realised accessing the mappings like this was possible.
  • JackThorpJackThorp Member Posts: 4
    @Atomrigs @cslarson @AronVanAmmers Apologies if I am asking for a re-explanation of something above. Is there a web3 call that will retrieve all the entries from a public map?
  • AronVanAmmersAronVanAmmers Amsterdam, NetherlandsMember Posts: 40 ✭✭
    @JackThorp nope: https://solidity.readthedocs.org/en/latest/types.html#mappings
    The key data is not actually stored in a mapping, only its sha3 hash used to look up the value.

    Because of this, mappings do not have a length or a concept of a key or value being “set”.
    So you'll always need to pass a key to get its value in the mapping. If you want to know what all the entries are, use an array or another data structure like a linked list.
  • JackThorpJackThorp Member Posts: 4
    @AronVanAmmers Great, thanks for the links! Looks like those Eris tutorials are pretty good! :smile:
Sign In or Register to comment.