bytes9 private _randomness;
function getRandomness() public view returns (uint256) {
return uint256(keccak256(abi.encode(_randomness, address(this))));
}
modifier updateRandomness() {
bytes32 randomness = _randomness;
assembly {
// Pick any of the last 256 blocks psuedorandomly for the blockhash.
// Store the blockhash, the current `randomness` and the `coinbase()`
// into the scratch space.
mstore(0x00, blockhash(sub(number(), add(1, byte(0, randomness)))))
// `randomness` is left-aligned.
// `coinbase()` is right-aligned.
// `difficulty()` is right-aligned.
// After the merge, if [EIP-4399](https://eips.ethereum.org/EIPS/eip-4399)
// is implemented, the randomness will be determined by the beacon chain.
mstore(0x20, xor(randomness, xor(coinbase(), difficulty())))
// Compute the new `randomness` by hashing the scratch space.
randomness := keccak256(0x00, 0x40)
}
_randomness = bytes9(randomness);
_;
}
function generateNFT() external updateRandomness {
uint256 randomNum = getRandomness();
uint256 remaining = MAX_SUPPLY - totalSupply();
uint256 newId = (randomNum % remaining);
// ...
}
seems like after the merge, getting a solid random value might be feasible.
this could be a good replacement for chainlink vrf?
For pseudorandomness you can use something like EIP-4399:
uint256 randomness = uint(keccak256(abi.encodePacked(msg.sender, block.difficulty, block.timestamp)));
(You don't even need to use assembly, difficulty
is directly exposed in solidity)
But for true randomness, you'd need something like Chainlink VRF.
function fulfillRandomWords(uint256 _requestId, uint256[] memory _randomWords) internal override {
uint256 randomness = _randomWords[0];
}
difficulty
or PREVRANDAO
pseudo-random in a post-merge ETH?When a validator is due to propose a block, they face a fairly minimal penalty for failing to do so. Given a smart contract that is trying to make itself unpredictable/uncontrollable via PREVRANDAO
, this option allows them to cheaply bias the behavior of the contract in the following ways.
PREVRANDAO
's value has no entropy given the PREVRANDAO
value from the previous block if no block is proposed. When you propose a block, that's a piece of the math equation that PREVRANDAO
uses to make a random number. So if you don't propose a block, you get a "less" random number.
So users of PREVRANDAO
would need to check that a validator has provided a block since the last time they called PREVRANDAO
. Otherwise, they won't necessarily be drawing statistically independent random outputs on successive calls to PREVRANDAO.
This means that the validator committed to proposing a particular block can effectively set the PREVRANDAO value for the next block to two possible values:
PREVRANDAO
input valuePREVRANDAO
gets deterministically set to if no block is proposedThis choice alone allows validators to influence the random number, no longer making the number random.
Even if the contract does something cleverer, like taking the PREVRANDAO
output from the first block to be proposed after some height, every validator after that height has the same option. No matter which way the contract tries to access PREVRANDAO
, the last validator to contribute will always have predictable control over the random output which controls the contract.
These issues don't arise for Chainlink VRF, because the output is deterministic, given the blockhash (just infeasible to compute for anyone who doesn't know the secret key.)
Once again, you still run into this issue from pre-merge. The penalty of not posting a block is almost negligible, so a node might be financially incentivized not to propose blocks in a lottery-type smart contract if the PREVRAND
value isn't what they want. Enough nodes do this and you run into issues.
Just taking the current PREVRANDAO
value without looking at recent history gives a validator some control over the output the contract will use, even if it doesn't want to abort, because the validator can decide whether or not the block it proposes contains a transaction which will trigger the use of PREVRANDAO. The only cost involved there is the transaction fee/tip.
PREVRANDAO
as their seedIf all applications use PREVRANDAO
as their seed, in a way, you could "chain" together wins or hacks based on this.
There is also an interesting thread on this in the Ethereum Magicians forum.