All the data on the blockchain is by its definition public, so whenever a contract relies on on-chain information that you can't directly access it can be vulnerable. Solidity variables can have different visibility (private, public, internal, external) but it's a mistake to think that private or internal variables can store secret values on the blockchain. This information is just not directly accessible by other contract but it's still visible to everyone who have access to the blockchain information.
This simple contract creates a pseudo-random number and you can try to guess it:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract RandomInt{
uint randomNum;
constructor () {
randomNum = uint(keccak256(abi.encodePacked(block.number, block.timestamp))) % 1000;
}
function guess(uint n) public view returns (bool){
return n == randomNum;
}
}
Whenever a contract tries to generate an on-chain random value it is creating a vulnerability: Solidity contracts are deterministic, so it is not possible to create on-chain randomness. Every random component of a contract should be done off-chain: https://blog.chain.link/random-number-generation-solidity/
Looking at this simple pseudo-random number generator we can guess the value in randomNum
just by looking at the contract memory.
We can create a simple script to visualize the contract memory with Python and the Web3.py library:
from web3 import Web3
url = 'YOUR_HTTPPROVIDER_ENDPOINT_URL'
provider = Web3(Web3.HTTPProvider(url))
contractAddress = Web3.toChecksumAddress('0xf9907D3b067F31430e5Cb49438Da545BC23EC967')
val = provider.eth.getStorageAt(contractAddress, 0)
print(int.from_bytes(val, "big"))
Where the command getStorageAt(contractAddress, index)
returns the value from a storage position for the given contract and its given block. The index comes from the order of the variables in the contract. The values are saved as bytes32, hex values 64 characters long.
With this simple python script we can get the random number generated.
If you want to try to do it again, here is the address of the contract used: 0xf9907D3b067F31430e5Cb49438Da545BC23EC967
.
Remembering that all information saved on the blockchain is public, when you have information that needs to be hidden, you need to use cryptography (commitment scheme or public-key cryptography). To get more information about the use of cryptography in smart contract: ethereum/EIPs#725.