0

Code -

pragma solidity ^0.4.18;

contract CA4 {


mapping(address=>uint16) public balances;

function getBalance() view returns (uint16) {
uint16 mybalance = balances[msg.sender];
return mybalance;
}

function setBalance(uint16 newbalance) public {
balances[msg.sender]=newbalance;
}


}

Geth -

> eth.getStorageAt(con.address,0)
"0x0000000000000000000000000000000000000000000000000000000000000000"
>  var key = web3.sha3("0x0000000000000000000000000000000000000000000000000000000000000000", {encoding:"hex"});
undefined
>  var bn = web3.toBigNumber(key);
undefined
> eth.getStorageAt(con.address, bn)
"0x0000000000000000000000000000000000000000000000000000000000000000"
> eth.getStorageAt(con.address,1)
"0x0000000000000000000000000000000000000000000000000000000000000000"
> con.getBalance()
1000

The array balances is stored somewhere. I am unsure how to find it. I tried a method which worked when we had an array of structs. But that did not work for the mapping of addresses to uint16.

How can we find the actual storage of balances using for example getStorageAt(con.address, slot)?

More Details -

> eth.defaultAccount
"0x46fb9a22689c4a4bfb494baeafbb8b2993725305"
> key=web3.sha3("0x46fb9a22689c4a4bfb494baeafbb8b2993725305",  {encoding:"hex"});
"0x0679d661b585d1b4f0c3fd18f943f19e7fa9777fe23cb96b94f469c20d6f0bd5"
> var bn = web3.toBigNumber(key);
undefined
> eth.getStorageAt(con.address, bn)
"0x0000000000000000000000000000000000000000000000000000000000000000"
> 
3

I finally solved it.

  1. The key is the actual key in the array.
  2. The pos is the displacement of that data definition from the start of the contract storage.

Then to solve this you just put the key (ie the address) into the a key+pos sha3 and convert to a bigNumber. The vital part also is to remove 0x and to format as 64 chars.

Here is the final output -

> key
"00000000000000000000000046fb9a22689c4a4bfb494baeafbb8b2993725305"
> key1
"000000000000000000000000f07044ba718e8eb25850be0e534f9522018784be"
> pos
"0000000000000000000000000000000000000000000000000000000000000001"
> 

Pos is 1 because I added a uint to the contract before the mapping to test this. The two keys are then the accounts with 0x removed and reformatted to 64 chars. ie.

> eth.coinbase
"0x46fb9a22689c4a4bfb494baeafbb8b2993725305"
> eth.accounts[1]
"0xf07044ba718e8eb25850be0e534f9522018784be"

> bn=web3.sha3(key + pos, {"encoding":"hex"})
"0x4a6915a70ddb253ab9075c26d94720491095a5a0a6d31c6720a4db10b12f661e"

> bn1=web3.sha3(key1 + pos, {"encoding":"hex"})
"0x4ecac4dae9466f9f535b4d7b3d80aedd57bdd0e6e6e74425ed865c2fa5e20707"

> sn1=web3.toBigNumber(bn1)
3.5638663940699095975789856456193070520684880277631386156092330925546436429575e+76

> sn=web3.toBigNumber(bn)
3.3656819177407030101749625369691302081266253965792325840314023187955028289054e+76

> eth.getStorageAt(con.address,sn1)
"0x0000000000000000000000000000000000000000000000000000000000001770"

> eth.getStorageAt(con.address,sn)
"0x0000000000000000000000000000000000000000000000000000000000001edc"

> con.getAddressBalance(eth.coinbase)
7900
> con.getAddressBalance(eth.accounts[1])
6000

Here is the amended contract -

pragma solidity ^0.4.18;

contract CA4 {

uint public pos;
mapping(address=>uint16) public balances;

function getBalance() view returns (uint16) {
uint16 mybalance = balances[msg.sender];
return mybalance;
}

function getAddressBalance (address myaddress) view returns (uint16) {
return balances[myaddress];
}

function setAddressBalance(address myaddress, uint16 newbalance) public {
pos=9;
balances[myaddress]=newbalance;
}

function setBalance(uint16 newbalance) public {
pos=9;
balances[msg.sender]=newbalance;
}


}
0

From the Solidity documentation:

The mapping or the dynamic array itself occupies an (unfilled) slot in storage at some position p according to the above rule (or by recursively applying this rule for mappings to mappings or arrays of arrays). For a dynamic array, this slot stores the number of elements in the array (byte arrays and strings are an exception here, see below). For a mapping, the slot is unused (but it is needed so that two equal mappings after each other will use a different hash distribution). Array data is located at keccak256(p) and the value corresponding to a mapping key k is located at keccak256(k . p) where . is concatenation. If the value is again a non-elementary type, the positions are found by adding an offset of keccak256(k . p).

Essentially, the mapping has a "root" slot in storage at location k. This slot contains no data. The actual values in the mapping are at keccak256(k+p), where p is a key. This means the objects in mappings are stored "randomly" in the storage space.

6
  • what is the key in my example? I am unsure. K=0? – Trevor Lee Oakley Feb 28 '18 at 17:21
  • It's whatever the key is. So if you have the mapping mapping(address => uint), where the root of the mapping is at storage slot 0x02, and you have key/value 0xdead=>42 in the mapping, then the location of 42 in storage is keccak256(0x02dead). – natewelch_ Feb 28 '18 at 17:25
  • I was thinking the key could be the address (msg.sender) but that did not work. I will update the post. – Trevor Lee Oakley Feb 28 '18 at 17:39
  • So in your case, yes, k is 0. K isn't the key though, k is just the location in storage of the root of the mapping. P is the key. Since your mapping is the first storage value, then it'll be 0. So the location of a value in your mapping will be keccak256(0x0+key) – natewelch_ Feb 28 '18 at 17:43
  • That was my logic. I tried the address (msg.sender) as the key but it did not find the storage location correctly. I posted all the geth output in the original question. – Trevor Lee Oakley Feb 28 '18 at 17:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.