Smart contracts are essentially public programs running on Ethereum, often taking on a portion of the backend role in DApp development. However, due to their public nature, data stored within them cannot be treated like a typical backend server or database. Even data set as can be accessed directly via storage slot positions. private This reminds us to thoroughly consider data structures when writing and to store sensitive data in backend databases, not in contracts. In this article, I'll demonstrate sensitive data access through a simple example. smart contracts Alice deployed a simple Vault contract to mimic a simplified backend database, storing user IDs and passwords. // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; contract Vault { // slot 0 uint public count = 123; // slot 1 address public owner = msg.sender; bool public isTrue = true; uint16 public u16 = 31; // slot 2 bytes32 private password; // constants do not use storage uint public constant someConst = 123; // slot 3, 4, 5 (one for each array element) bytes32[3] public data; struct User { uint id; bytes32 password; } // slot 6 - length of array // starting from slot hash(6) - array elements // slot where array element is stored = keccak256(slot)) + (index * elementSize) // where slot = 6 and elementSize = 2 (1 (uint) + 1 (bytes32)) User[] private users; // slot 7 - empty // entries are stored at hash(key, slot) // where slot = 7, key = map key mapping(uint => User) private idToUser; constructor(bytes32 _password) { password = _password; } function addUser(bytes32 _password) public { User memory user = User({id: users.length, password: _password}); users.push(user); idToUser[user.id] = user; } function getArrayLocation( uint slot, uint index, uint elementSize ) public pure returns (uint) { return uint(keccak256(abi.encodePacked(slot))) + (index * elementSize); } function getMapLocation(uint slot, uint key) public pure returns (uint) { return uint(keccak256(abi.encodePacked(key, slot))); } } Variable Storage in Smart Contracts 📊🔍 In , a slot can hold 32 bytes, equivalent to 256 bits. The allocation of storage for variables in smart contracts depends on several factors: the type of variable, the size it occupies in storage, and their declaration order. Here are some key principles for determining variable storage in a contract: Ethereum smart contracts If a variable occupies 32 bytes or 256 bits, it always occupies a single slot on its own. For example, a or type variable. byte32 uint256 Variables of dynamic size always occupy a single slot on their own, such as or . bytes string If a variable doesn't occupy 32 bytes or 256 bits, it might be packed with adjacent variables in the same slot automatically. For instance, Ethereum's is a 20-byte byte string. only occupies one byte (actually, just a bit, but the smallest storage unit is a byte). occupies 16 bits or 2 bytes. In the packing process, if a large variable or a variable of dynamic size is encountered, the packing process stops, and the new variable is stored in the next adjacent slot. address bool uint16 Elements of a fixed array are stored directly in consecutive slots. If array elements are types that are less than or equal to 32 bytes, multiple elements might be packed in the same slot. However, if an element type itself occupies an entire slot, each element will occupy its own slot. Dynamic-length arrays are stored in two parts. The length of the array occupies a single slot. The position of the elements is based on the slot's position, the size of the element, and the element's index. For example, if we declare a dynamic array in the third slot, i.e., slot 2, its length is stored in slot 2, and its first element is stored at: . uint(keccak256(abi.encodePacked(2))) A declared causes an empty slot to appear, but neither the key nor the value of the mapping is stored in this slot. The key of the mapping is not stored, and the value's position is calculated based on the position of this empty slot and the key. For instance, if we declare a in the third slot, i.e., slot 2, and suppose it has a key of 10, then the position of the value corresponding to this key is at . mapping mapping(uint => User) private idToUser uint(keccak256(abi.encodePacked(10, 2))) A doesn't directly occupy a slot when defined. It only occupies storage slots when instances of the struct are used as state variables (such as array elements or mapping values). Each struct instance's storage method depends on how it is used. For example, if used as elements of an array or values of a mapping, it must conform to the storage mechanism of these data structures. struct How to Access the Database Password and User Passwords in the Example Contract 🔓💡 The example contract first defines a , which, by default, is , occupying 32 bytes and thus a single slot 0. The next three variables defined occupy 20 bytes, 1 byte, and 2 bytes, respectively, and are packed together in slot 1. The Vault's password occupies its own slot. Although set as , we can directly access it through its slot position. uint uint256 private Each user is a struct, stored in both array and mapping. The user's password is saved in this struct. Thus, although both the array and mapping are set to , we can still calculate the slot position to obtain the user's password. User[] private users; mapping(uint => User) private idToUser; private The function demonstrates how to calculate the storage slot position of the corresponding user struct from the array. The statement returns the starting position of the user struct stored at the index. After we obtain this position, we can further obtain the user's password based on the internal data structure of the struct. Inside the structure, there is a and a , both occupying one slot each. Therefore, the password is stored in the slot returned by the function + 1. getArrayLocation return uint(keccak256(abi.encodePacked(slot))) + (index * elementSize); uint bytes32 getArrayLocation The function demonstrates how to calculate the storage slot position of the corresponding user struct from the mapping. The statement returns the starting position of the user struct stored at the key (i.e., id). After we obtain this position, we can also obtain the user's password based on the internal data structure of the struct. getMapLocation return uint(keccak256(abi.encodePacked(key, slot))); This is why we should never treat contracts as regular backends and store sensitive data within them. If similar needs arise, we still need to rely on backend servers and databases to solve them. That's all about bypassing the modifier to directly read sensitive data and related security strategies. If you've made it this far, help me out with a like! 👍🙂📝 private