// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
contract Privacy {
bool public locked = true;
uint256 public ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;
constructor(bytes32[3] memory _data) public {
data = _data;
}
function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}
/*
A bunch of super advanced solidity algorithms...
,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}
This is similar to the 8th level Vault, where we read the EVM storage. Here in addition to that, we learn about a small optimization of EVM and how casting works.
EVM stores state variables in chunks of 32 bytes. If consecutive variables make up a 32-byte space (such as in this case 8 + 8 + 16 = 32) they are stored in the same chunk. If you were to write them elsewhere, this optimization may not have happened. Let us check the results of await web3.eth.getStorageAt(contract.address, i) for the following values of i:
-
0: 0x0000000000000000000000000000000000000000000000000000000000000001This is thebool public locked = truewhich is stored as 1. -
1: 0x0000000000000000000000000000000000000000000000000000000062bc6f36This is theuint256 public ID = block.timestampwhich is the UNIX timestamp in hex,62bc6f36(of this block in my instance]) -
2: 0x000000000000000000000000000000000000000000000000000000006f36ff0aThis is the 32 byte chunk of 3 variables all captures in6f36ff0a:-
uint8 private flattening = 10which is0a -
uint8 private denomination = 255which isff -
uint16 private awkwardness = uint16(now)which is6f36. Well, thatawkwardnessvariable is just theblock.timestampcasted to 16-bits. We already know the actual 256-bit (32-byte) value of timestamp above:62bc6f36. When casted down 16-bits, it became6f36(4 x 4-bit hexadecimals).
-
-
3: 0x0ec18718027136372f96fb04400e05bac5ba7feda24823118503bff40bc5eb55This isdata[0]. -
4: 0x61a99635e6d4b7233a35f3d0d5d8fadf2981d424110e8bca127d64958d1e68c0This isdata[1]. -
5: 0x46b7d5d54e84dc3ac47f57bea2ca5f79c04dadf65d3a0f3581dcad259f9480cfThis isdata[2].
Now we just need data[2] casted down to bytes16. Here is how casting works in very few words:
- Conversion to smaller type costs more signficant bits. (e.g.
uint32 -> uint16) - Conversion to higher type adds padding bits to the left. (e.g.
uint16 -> uint32) - Conversion to smaller byte costs less significant bits. (e.g.
bytes32 -> bytes16) - Conversion to larger byte add padding bits to the right. (e.g.
bytes16 -> bytes32)
So, when we cast down data[2] we will get the left-half of it: '0x46b7d5d54e84dc3ac47f57bea2ca5f79c04dadf65d3a0f3581dcad259f9480cf'.slice(0, 2 + 32) and then await contract.unlock('0x46b7d5d54e84dc3ac47f57bea2ca5f79'). That is all! Here is a good article on reading storage: https://medium.com/@dariusdev/how-to-read-ethereum-contract-storage-44252c8af925.
Top comments (0)