From eaa70dcfa6fd62ee617459db1b09d04c89555e95 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Thu, 1 Sep 2022 02:32:22 +1000 Subject: [PATCH 1/6] fix(RNG): store the RN value and combine BeaconRNG --- contracts/standard/rng/BeaconRNG.sol | 58 ++++++++++++--- contracts/standard/rng/BeaconRNGFallback.sol | 76 -------------------- 2 files changed, 48 insertions(+), 86 deletions(-) delete mode 100644 contracts/standard/rng/BeaconRNGFallback.sol diff --git a/contracts/standard/rng/BeaconRNG.sol b/contracts/standard/rng/BeaconRNG.sol index e286e337..f5a0e5c7 100644 --- a/contracts/standard/rng/BeaconRNG.sol +++ b/contracts/standard/rng/BeaconRNG.sol @@ -1,6 +1,6 @@ /** - * @authors: [@shalzz] - * @reviewers: [@jaybuidl] + * @authors: [@shalzz, @unknownunknown1] + * @reviewers: [@jaybuidl*, @geaxed*] * @auditors: [] * @bounties: [] * @deployments: [] @@ -14,9 +14,21 @@ import "./RNG.sol"; * @title Random Number Generator using beacon chain random opcode */ contract BeaconRNG is RNG { + + uint public constant LOOKAHEAD = 132; // Number of blocks that has to pass before obtaining the random number. 4 epochs + 4 slots, according to EIP-4399. + + RNG public blockhashRNG; // Address of blockhashRNG to fall back on. + mapping (uint => uint) public randomNumber; // randomNumber[block] is the random number for this requested block, 0 otherwise. + + /** @dev Constructor. + * @param _blockhashRNG The blockhash RNG deployed contract address. + */ + constructor(RNG _blockhashRNG) public { + blockhashRNG = _blockhashRNG; + } /** - * @dev Since we don't really need to incentivise requesting the beacon chain randomness, + * @dev Since we don't really need to incentivize requesting the beacon chain randomness, * this is a stub implementation required for backwards compatibility with the * RNG interface. * @notice All the ETH sent here will be lost forever. @@ -24,15 +36,41 @@ contract BeaconRNG is RNG { */ function contribute(uint _block) public payable {} + /** + * @dev Request a random number. + * @dev Since the beacon chain randomness is not related to a block + * we can call ahead its getRN function to check if the PoS merge has happened or not. + * + * @param _block Block linked to the request. + */ + function requestRN(uint _block) public payable { + // Use the old RNG pre-Merge. + if (block.difficulty <= 2**64) { + blockhashRNG.contribute(_block); + } else { + contribute(_block); + } + } - /** @dev Return the random number from the PoS randomness beacon. - * @param _block Block the random number is linked to. - * @return RN Random Number. If the PoS upgrade defined by EIP-3675 - * has not yet executed 0 instead. + /** + * @dev Get the random number. + * @param _block Block the random number is linked to. + * @return RN Random Number. If the number is not ready or has not been required 0 instead. */ - function getRN(uint _block) public returns (uint RN) { - if (block.difficulty <= 2**64) + function getRN(uint _block) public returns (uint) { + // if beacon chain randomness is zero + // fallback to blockhash RNG + if (block.difficulty <= 2**64) { + return blockhashRNG.getRN(_block); + } else if (block.number < _block + LOOKAHEAD) { + // Beacon chain returns the random number, but sufficient number of blocks hasn't been mined yet. + // In this case signal to the court that RN isn't ready. return 0; - return block.difficulty; + } else { + if (randomNumber[_block] == 0) { + randomNumber[_block] = block.difficulty; + } + return randomNumber[_block]; + } } } diff --git a/contracts/standard/rng/BeaconRNGFallback.sol b/contracts/standard/rng/BeaconRNGFallback.sol deleted file mode 100644 index 54c5f9df..00000000 --- a/contracts/standard/rng/BeaconRNGFallback.sol +++ /dev/null @@ -1,76 +0,0 @@ - /** - * @authors: [@shalzz, @unknownunknown1] - * @reviewers: [@jaybuidl, @geaxed*] - * @auditors: [] - * @bounties: [] - * @deployments: [] - */ - -pragma solidity ^0.4.15; - -import "./RNG.sol"; - -/** - * @title Random Number Generator using beacon chain random opcode - */ -contract BeaconRNGFallBack is RNG { - - RNG public beaconRNG; - RNG public blockhashRNG; - - uint public constant LOOKAHEAD = 132; // Number of blocks that has to pass before obtaining the random number. 4 epochs + 4 slots, according to EIP-4399. - - /** @dev Constructor. - * @param _beaconRNG The beacon chain RNG deployed contract address - * @param _blockhashRNG The blockhash RNG deployed contract address - */ - constructor(RNG _beaconRNG, RNG _blockhashRNG) public { - beaconRNG = _beaconRNG; - blockhashRNG = _blockhashRNG; - } - - /** - * @dev Since we don't really need to incentivize requesting the beacon chain randomness, - * this is a stub implementation required for backwards compatibility with the - * RNG interface. - * @notice All the ETH sent here will be lost forever. - * @param _block Block the random number is linked to. - */ - function contribute(uint _block) public payable {} - - /** - * @dev Request a random number. - * @dev Since the beacon chain randomness is not related to a block - * we can call ahead its getRN function to check if the PoS merge has happened or not. - * - * @param _block Block linked to the request. - */ - function requestRN(uint _block) public payable { - uint RN = beaconRNG.getRN(_block); - - if (RN == 0) { - blockhashRNG.contribute(_block); - } else { - beaconRNG.contribute(_block); - } - } - - /** - * @dev Get the random number. - * @param _block Block the random number is linked to. - * @return RN Random Number. If the number is not ready or has not been required 0 instead. - */ - function getRN(uint _block) public returns (uint RN) { - RN = beaconRNG.getRN(_block); - - // if beacon chain randomness is zero - // fallback to blockhash RNG - if (RN == 0) { - RN = blockhashRNG.getRN(_block); - } else if (block.number < _block + LOOKAHEAD) { - // Beacon chain returns the random number, but sufficient number of blocks hasn't been mined yet. - // In this case signal to the court that RN isn't ready. - RN = 0; - } - } -} From c49bc905d912151254cbd83d23e4da46a04c2319 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Thu, 1 Sep 2022 18:48:55 +1000 Subject: [PATCH 2/6] feat(RNG): add limit for blocks to pass --- contracts/standard/rng/BeaconRNG.sol | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/contracts/standard/rng/BeaconRNG.sol b/contracts/standard/rng/BeaconRNG.sol index f5a0e5c7..56121a5d 100644 --- a/contracts/standard/rng/BeaconRNG.sol +++ b/contracts/standard/rng/BeaconRNG.sol @@ -16,9 +16,11 @@ import "./RNG.sol"; contract BeaconRNG is RNG { uint public constant LOOKAHEAD = 132; // Number of blocks that has to pass before obtaining the random number. 4 epochs + 4 slots, according to EIP-4399. + uint public constant ERROR = 20; // Number of blocks after which the lookahead gets reset, so eligible blocks after lookahead don't go long distance, to avoid a possiblity for manipulation. RNG public blockhashRNG; // Address of blockhashRNG to fall back on. - mapping (uint => uint) public randomNumber; // randomNumber[block] is the random number for this requested block, 0 otherwise. + mapping (uint => uint) public randomNumber; // randomNumber[_requestedBlock] is the random number for this requested block, 0 otherwise. + mapping (uint => uint) public startingBlock; // The starting block number for lookahead countdown. startingBlock[_requestedBlock]. /** @dev Constructor. * @param _blockhashRNG The blockhash RNG deployed contract address. @@ -48,6 +50,9 @@ contract BeaconRNG is RNG { if (block.difficulty <= 2**64) { blockhashRNG.contribute(_block); } else { + if (startingBlock[_block] == 0) { + startingBlock[_block] = _block; // Starting block is equal to requested by default. + } contribute(_block); } } @@ -62,11 +67,17 @@ contract BeaconRNG is RNG { // fallback to blockhash RNG if (block.difficulty <= 2**64) { return blockhashRNG.getRN(_block); - } else if (block.number < _block + LOOKAHEAD) { - // Beacon chain returns the random number, but sufficient number of blocks hasn't been mined yet. - // In this case signal to the court that RN isn't ready. - return 0; } else { + // Reset the starting block if too many blocks passed since lookahead. + if (block.number > startingBlock[_block] + LOOKAHEAD + ERROR) { + startingBlock[_block] = block.number; + } + if (block.number < startingBlock[_block] + LOOKAHEAD) { + // Beacon chain returns the random number, but sufficient number of blocks hasn't been mined yet. + // In this case signal to the court that RN isn't ready. + return 0; + } + if (randomNumber[_block] == 0) { randomNumber[_block] = block.difficulty; } From e1718e5322fec7786caeaaaf61486b1f667dca71 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Fri, 2 Sep 2022 04:50:43 +1000 Subject: [PATCH 3/6] fix(RN): change ERROR limit to 32 --- contracts/standard/rng/BeaconRNG.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/standard/rng/BeaconRNG.sol b/contracts/standard/rng/BeaconRNG.sol index 56121a5d..d2212664 100644 --- a/contracts/standard/rng/BeaconRNG.sol +++ b/contracts/standard/rng/BeaconRNG.sol @@ -16,7 +16,7 @@ import "./RNG.sol"; contract BeaconRNG is RNG { uint public constant LOOKAHEAD = 132; // Number of blocks that has to pass before obtaining the random number. 4 epochs + 4 slots, according to EIP-4399. - uint public constant ERROR = 20; // Number of blocks after which the lookahead gets reset, so eligible blocks after lookahead don't go long distance, to avoid a possiblity for manipulation. + uint public constant ERROR = 32; // Number of blocks after which the lookahead gets reset, so eligible blocks after lookahead don't go long distance, to avoid a possiblity for manipulation. RNG public blockhashRNG; // Address of blockhashRNG to fall back on. mapping (uint => uint) public randomNumber; // randomNumber[_requestedBlock] is the random number for this requested block, 0 otherwise. From e20dcd9d25744edda53b1d236fa68ba0dc9dc969 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Fri, 2 Sep 2022 20:11:41 +1000 Subject: [PATCH 4/6] fix(RNG): simplification --- contracts/standard/rng/BeaconRNG.sol | 67 +++++++++------------------- 1 file changed, 22 insertions(+), 45 deletions(-) diff --git a/contracts/standard/rng/BeaconRNG.sol b/contracts/standard/rng/BeaconRNG.sol index d2212664..0d62607d 100644 --- a/contracts/standard/rng/BeaconRNG.sol +++ b/contracts/standard/rng/BeaconRNG.sol @@ -13,14 +13,13 @@ import "./RNG.sol"; /** * @title Random Number Generator using beacon chain random opcode */ -contract BeaconRNG is RNG { +contract BeaconRNG { uint public constant LOOKAHEAD = 132; // Number of blocks that has to pass before obtaining the random number. 4 epochs + 4 slots, according to EIP-4399. uint public constant ERROR = 32; // Number of blocks after which the lookahead gets reset, so eligible blocks after lookahead don't go long distance, to avoid a possiblity for manipulation. RNG public blockhashRNG; // Address of blockhashRNG to fall back on. - mapping (uint => uint) public randomNumber; // randomNumber[_requestedBlock] is the random number for this requested block, 0 otherwise. - mapping (uint => uint) public startingBlock; // The starting block number for lookahead countdown. startingBlock[_requestedBlock]. + mapping (uint => uint) public randomNumber; // randomNumber[_block] is the random number for this requested block, 0 otherwise. /** @dev Constructor. * @param _blockhashRNG The blockhash RNG deployed contract address. @@ -30,58 +29,36 @@ contract BeaconRNG is RNG { } /** - * @dev Since we don't really need to incentivize requesting the beacon chain randomness, - * this is a stub implementation required for backwards compatibility with the - * RNG interface. - * @notice All the ETH sent here will be lost forever. - * @param _block Block the random number is linked to. - */ - function contribute(uint _block) public payable {} - - /** - * @dev Request a random number. - * @dev Since the beacon chain randomness is not related to a block - * we can call ahead its getRN function to check if the PoS merge has happened or not. - * - * @param _block Block linked to the request. + * @dev Request a random number. It is not used by this contract and only exists for backward compatibility. */ - function requestRN(uint _block) public payable { - // Use the old RNG pre-Merge. - if (block.difficulty <= 2**64) { - blockhashRNG.contribute(_block); - } else { - if (startingBlock[_block] == 0) { - startingBlock[_block] = _block; // Starting block is equal to requested by default. - } - contribute(_block); - } - } + function requestRN(uint /*_block*/) public pure {} /** - * @dev Get the random number. + * @dev Get aт uncorrelated random number. * @param _block Block the random number is linked to. * @return RN Random Number. If the number is not ready or has not been required 0 instead. */ - function getRN(uint _block) public returns (uint) { - // if beacon chain randomness is zero - // fallback to blockhash RNG + function getUncorrelatedRN(uint _block) public returns (uint) { + // Pre-Merge. if (block.difficulty <= 2**64) { - return blockhashRNG.getRN(_block); - } else { - // Reset the starting block if too many blocks passed since lookahead. - if (block.number > startingBlock[_block] + LOOKAHEAD + ERROR) { - startingBlock[_block] = block.number; - } - if (block.number < startingBlock[_block] + LOOKAHEAD) { - // Beacon chain returns the random number, but sufficient number of blocks hasn't been mined yet. - // In this case signal to the court that RN isn't ready. + uint baseRN = blockhashRNG.getRN(_block); + if (baseRN == 0) { return 0; + } else { + return uint(keccak256(abi.encodePacked(msg.sender, baseRN))); } - - if (randomNumber[_block] == 0) { - randomNumber[_block] = block.difficulty; + // Post-Merge. + } else { + if (block.number > _block && (block.number - _block) % (LOOKAHEAD + ERROR) > LOOKAHEAD) { + // Eligible block number should exceed LOOKAHEAD but shouldn't be higher than LOOKAHEAD + ERROR. + // In case of the latter LOOKAHEAD gets reset. + if (randomNumber[_block] == 0) { + randomNumber[_block] = block.difficulty; + } + return uint(keccak256(abi.encodePacked(msg.sender, randomNumber[_block]))); + } else { + return 0; } - return randomNumber[_block]; } } } From 12eec7340de3cf5fc18e8c43e38c1273adcd12f2 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Fri, 2 Sep 2022 20:53:34 +1000 Subject: [PATCH 5/6] fix(RN): remove storing --- contracts/standard/rng/BeaconRNG.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contracts/standard/rng/BeaconRNG.sol b/contracts/standard/rng/BeaconRNG.sol index 0d62607d..e46b7d20 100644 --- a/contracts/standard/rng/BeaconRNG.sol +++ b/contracts/standard/rng/BeaconRNG.sol @@ -19,7 +19,6 @@ contract BeaconRNG { uint public constant ERROR = 32; // Number of blocks after which the lookahead gets reset, so eligible blocks after lookahead don't go long distance, to avoid a possiblity for manipulation. RNG public blockhashRNG; // Address of blockhashRNG to fall back on. - mapping (uint => uint) public randomNumber; // randomNumber[_block] is the random number for this requested block, 0 otherwise. /** @dev Constructor. * @param _blockhashRNG The blockhash RNG deployed contract address. @@ -52,10 +51,7 @@ contract BeaconRNG { if (block.number > _block && (block.number - _block) % (LOOKAHEAD + ERROR) > LOOKAHEAD) { // Eligible block number should exceed LOOKAHEAD but shouldn't be higher than LOOKAHEAD + ERROR. // In case of the latter LOOKAHEAD gets reset. - if (randomNumber[_block] == 0) { - randomNumber[_block] = block.difficulty; - } - return uint(keccak256(abi.encodePacked(msg.sender, randomNumber[_block]))); + return uint(keccak256(abi.encodePacked(msg.sender, block.difficulty))); } else { return 0; } From d980bbb8f3e7147ee2ee6bda66ba0231de75c0b7 Mon Sep 17 00:00:00 2001 From: unknownunknown1 Date: Fri, 2 Sep 2022 20:56:13 +1000 Subject: [PATCH 6/6] doc(RN): typo --- contracts/standard/rng/BeaconRNG.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/standard/rng/BeaconRNG.sol b/contracts/standard/rng/BeaconRNG.sol index e46b7d20..95eb9036 100644 --- a/contracts/standard/rng/BeaconRNG.sol +++ b/contracts/standard/rng/BeaconRNG.sol @@ -33,7 +33,7 @@ contract BeaconRNG { function requestRN(uint /*_block*/) public pure {} /** - * @dev Get aт uncorrelated random number. + * @dev Get an uncorrelated random number. * @param _block Block the random number is linked to. * @return RN Random Number. If the number is not ready or has not been required 0 instead. */