From 596d81eb4d7ce655acd77b5d04ea43a19cc98a33 Mon Sep 17 00:00:00 2001 From: Daniel Berger Date: Fri, 28 Jun 2024 15:42:50 -0400 Subject: [PATCH 1/4] Create MockEntropy.sol --- .../entropy_sdk/solidity/MockEntropy.sol | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol diff --git a/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol b/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol new file mode 100644 index 0000000000..b9c7e0c433 --- /dev/null +++ b/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.0; + +interface IEntropyConsumer { + function _entropyCallback( + uint64 sequenceNumber, + address provider, + bytes32 randomNumber + ) external; +} + +contract MockEntropy { + uint64 public sequenceNumber; + uint128 public constant FEE = 0.000015 ether; + + function getDefaultProvider() external view returns (address) { + return address(this); + } + + function getFee(address provider) external pure returns (uint128) { + require(provider != address(0), "Invalid provider address"); + return FEE; + } + + function requestWithCallback( + address provider, + bytes32 userRandomNumber + ) external payable returns (uint64) { + require(provider != address(0), "Invalid provider address"); + require(msg.value >= FEE, "Not enough ether sent for fee"); + return sequenceNumber++; + } + + function triggerCallback( + uint64 _sequenceNumber, + uint256 _randomNumber, + address _callbackAddress + ) external { + bytes32 randomNumberBytes = bytes32(_randomNumber); + IEntropyConsumer(_callbackAddress)._entropyCallback( + _sequenceNumber, + _callbackAddress, + randomNumberBytes + ); + } +} From e65e0bc7b62d638a70a3d94a0111b60f20fa8912 Mon Sep 17 00:00:00 2001 From: Daniel Berger Date: Mon, 1 Jul 2024 16:26:55 -0400 Subject: [PATCH 2/4] Revise MockEntropy.sol --- .../entropy_sdk/solidity/MockEntropy.sol | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol b/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol index b9c7e0c433..125c57b813 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol +++ b/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol @@ -1,46 +1,58 @@ // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.0; -interface IEntropyConsumer { - function _entropyCallback( - uint64 sequenceNumber, - address provider, - bytes32 randomNumber - ) external; -} +import {IEntropyConsumer} from "./IEntropyConsumer.sol"; contract MockEntropy { - uint64 public sequenceNumber; - uint128 public constant FEE = 0.000015 ether; + uint256 public fee; + + mapping(uint256 => address) public callbackRequests; + uint64 public currentSequenceNumber; + + constructor(uint256 _fee) { + fee = _fee; + } function getDefaultProvider() external view returns (address) { return address(this); } - function getFee(address provider) external pure returns (uint128) { - require(provider != address(0), "Invalid provider address"); - return FEE; + function getFee( + address _provider + ) external view isProvider(_provider) returns (uint256) { + return fee; + } + + function setFee(uint256 _fee) external { + fee = _fee; } function requestWithCallback( - address provider, - bytes32 userRandomNumber - ) external payable returns (uint64) { - require(provider != address(0), "Invalid provider address"); - require(msg.value >= FEE, "Not enough ether sent for fee"); - return sequenceNumber++; + address _provider, + bytes32 _userRandomNumber + ) external payable isProvider(_provider) returns (uint64) { + require(msg.value >= fee, "Not enough ether sent for fee"); + callbackRequests[currentSequenceNumber] = msg.sender; + return currentSequenceNumber++; } function triggerCallback( - uint64 _sequenceNumber, uint256 _randomNumber, - address _callbackAddress + uint64 _sequenceNumber ) external { + address callbackAddress = callbackRequests[_sequenceNumber]; + require(callbackAddress != address(0), "No pending request"); bytes32 randomNumberBytes = bytes32(_randomNumber); - IEntropyConsumer(_callbackAddress)._entropyCallback( + IEntropyConsumer(callbackAddress)._entropyCallback( _sequenceNumber, - _callbackAddress, + callbackAddress, randomNumberBytes ); + callbackRequests[_sequenceNumber] = address(0); + } + + modifier isProvider(address _provider) { + require(_provider == address(this), "Invalid provider address"); + _; } } From 182b1bc814dcf143ac11b3b1f3e78fa666d9ff6f Mon Sep 17 00:00:00 2001 From: Daniel Berger Date: Tue, 2 Jul 2024 17:14:19 -0400 Subject: [PATCH 3/4] Revise MockEntropy.sol Removed unnecessary "_" in inputs Utilized EntropyErrors.sol errors --- .../entropy_sdk/solidity/MockEntropy.sol | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol b/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol index 125c57b813..86fd3df857 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol +++ b/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.0; import {IEntropyConsumer} from "./IEntropyConsumer.sol"; +import "./EntropyErrors.sol"; contract MockEntropy { uint256 public fee; @@ -18,8 +19,8 @@ contract MockEntropy { } function getFee( - address _provider - ) external view isProvider(_provider) returns (uint256) { + address provider + ) external view isProvider(provider) returns (uint256) { return fee; } @@ -28,31 +29,37 @@ contract MockEntropy { } function requestWithCallback( - address _provider, - bytes32 _userRandomNumber - ) external payable isProvider(_provider) returns (uint64) { - require(msg.value >= fee, "Not enough ether sent for fee"); + address provider, + bytes32 userRandomNumber + ) external payable isProvider(provider) returns (uint64) { + if (msg.value < fee) { + revert EntropyErrors.InsufficientFee(); + } callbackRequests[currentSequenceNumber] = msg.sender; return currentSequenceNumber++; } function triggerCallback( - uint256 _randomNumber, - uint64 _sequenceNumber + uint256 randomNumber, + uint64 sequenceNumber ) external { - address callbackAddress = callbackRequests[_sequenceNumber]; - require(callbackAddress != address(0), "No pending request"); - bytes32 randomNumberBytes = bytes32(_randomNumber); + address callbackAddress = callbackRequests[sequenceNumber]; + if (callbackAddress == address(0)) { + revert EntropyErrors.NoSuchRequest(); + } + bytes32 randomNumberBytes = bytes32(randomNumber); IEntropyConsumer(callbackAddress)._entropyCallback( - _sequenceNumber, - callbackAddress, + sequenceNumber, + address(this), randomNumberBytes ); - callbackRequests[_sequenceNumber] = address(0); + callbackRequests[sequenceNumber] = address(0); } - modifier isProvider(address _provider) { - require(_provider == address(this), "Invalid provider address"); + modifier isProvider(address provider) { + if (provider != address(this)) { + revert EntropyErrors.NoSuchProvider(); + } _; } } From bbf16c357c30a06e9c7748be750793120fc566db Mon Sep 17 00:00:00 2001 From: Daniel Berger Date: Thu, 18 Jul 2024 17:54:05 -0400 Subject: [PATCH 4/4] Implement IEntropy --- .../entropy_sdk/solidity/MockEntropy.sol | 493 ++++++++++++++++-- .../entropy_sdk/solidity/MockEntropyState.sol | 23 + 2 files changed, 472 insertions(+), 44 deletions(-) create mode 100644 target_chains/ethereum/entropy_sdk/solidity/MockEntropyState.sol diff --git a/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol b/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol index 86fd3df857..6d6f80f856 100644 --- a/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol +++ b/target_chains/ethereum/entropy_sdk/solidity/MockEntropy.sol @@ -1,65 +1,470 @@ // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.0; -import {IEntropyConsumer} from "./IEntropyConsumer.sol"; +import "./EntropyStructs.sol"; import "./EntropyErrors.sol"; +import "./EntropyEvents.sol"; +import "./IEntropy.sol"; +import "./IEntropyConsumer.sol"; +import "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import "./MockEntropyState.sol"; -contract MockEntropy { - uint256 public fee; +contract MockEntropy is IEntropy, MockEntropyState { + error NotImplemented(); - mapping(uint256 => address) public callbackRequests; - uint64 public currentSequenceNumber; + function _initialize( + address admin, + uint128 pythFeeInWei, + address defaultProvider, + bool prefillRequestStorage + ) internal { + require(admin != address(0), "admin is zero address"); + require(defaultProvider != address(0), "defaultProvider is zero address"); - constructor(uint256 _fee) { - fee = _fee; + _state.admin = admin; + _state.accruedPythFeesInWei = 0; + _state.pythFeeInWei = pythFeeInWei; + _state.defaultProvider = defaultProvider; + + if (prefillRequestStorage) { + for (uint8 i = 0; i < NUM_REQUESTS; i++) { + EntropyStructs.Request storage req = _state.requests[i]; + req.provider = address(1); + req.blockNumber = 1234; + req.commitment = hex"0123"; + } } + } + + constructor( + address admin, + uint128 pythFeeInWei, + address defaultProvider, + bool prefillRequestStorage, + uint128 feeInWei, + bytes32 commitment, + bytes memory commitmentMetadata, + uint64 chainLength, + bytes memory uri + ) { + _initialize(admin, pythFeeInWei, defaultProvider, prefillRequestStorage); + + EntropyStructs.ProviderInfo storage provider = _state.providers[ + defaultProvider + ]; + + provider.feeInWei = feeInWei; + provider.originalCommitment = commitment; + provider.originalCommitmentSequenceNumber = provider.sequenceNumber; + provider.currentCommitment = commitment; + provider.currentCommitmentSequenceNumber = provider.sequenceNumber; + provider.commitmentMetadata = commitmentMetadata; + provider.endSequenceNumber = provider.sequenceNumber + chainLength; + provider.uri = uri; + provider.sequenceNumber += 1; + + emit Registered(provider); + } + + // Not implemented + function register( + uint128 feeInWei, + bytes32 commitment, + bytes calldata commitmentMetadata, + uint64 chainLength, + bytes calldata uri + ) public override { + revert NotImplemented(); + } + function withdraw(uint128 amount) public override { + EntropyStructs.ProviderInfo storage providerInfo = _state.providers[ + getDefaultProvider() + ]; + + require(providerInfo.accruedFeesInWei >= amount, "Insufficient balance"); + providerInfo.accruedFeesInWei -= amount; + + (bool sent, ) = msg.sender.call{value: amount}(""); + require(sent, "withdrawal to msg.sender failed"); + + emit Withdrawal(getDefaultProvider(), msg.sender, amount); + } - function getDefaultProvider() external view returns (address) { - return address(this); + function withdrawAsFeeManager( + address provider, + uint128 amount + ) external override { + EntropyStructs.ProviderInfo storage providerInfo = _state.providers[ + provider + ]; + + if (providerInfo.sequenceNumber == 0) { + revert EntropyErrors.NoSuchProvider(); } - function getFee( - address provider - ) external view isProvider(provider) returns (uint256) { - return fee; + if (providerInfo.feeManager != msg.sender) { + revert EntropyErrors.Unauthorized(); } - function setFee(uint256 _fee) external { - fee = _fee; + require(providerInfo.accruedFeesInWei >= amount, "Insufficient balance"); + providerInfo.accruedFeesInWei -= amount; + + (bool sent, ) = msg.sender.call{value: amount}(""); + require(sent, "withdrawal to msg.sender failed"); + + emit Withdrawal(provider, msg.sender, amount); + } + + function requestHelper( + address provider, + bytes32 userCommitment, + bool useBlockhash, + bool isRequestWithCallback + ) internal returns (EntropyStructs.Request storage req) { + EntropyStructs.ProviderInfo storage providerInfo = _state.providers[ + provider + ]; + if (_state.providers[provider].sequenceNumber == 0) + revert EntropyErrors.NoSuchProvider(); + + uint64 assignedSequenceNumber = providerInfo.sequenceNumber; + if (assignedSequenceNumber >= providerInfo.endSequenceNumber) + revert EntropyErrors.OutOfRandomness(); + providerInfo.sequenceNumber += 1; + + uint128 requiredFee = getFee(provider); + if (msg.value < requiredFee) revert EntropyErrors.InsufficientFee(); + providerInfo.accruedFeesInWei += providerInfo.feeInWei; + _state.accruedPythFeesInWei += (SafeCast.toUint128(msg.value) - + providerInfo.feeInWei); + + req = allocRequest(provider, assignedSequenceNumber); + req.provider = provider; + req.sequenceNumber = assignedSequenceNumber; + req.numHashes = SafeCast.toUint32( + assignedSequenceNumber - providerInfo.currentCommitmentSequenceNumber + ); + req.commitment = keccak256( + bytes.concat(userCommitment, providerInfo.currentCommitment) + ); + req.requester = msg.sender; + + req.blockNumber = SafeCast.toUint64(block.number); + req.useBlockhash = useBlockhash; + req.isRequestWithCallback = isRequestWithCallback; + } + + function request( + address provider, + bytes32 userCommitment, + bool useBlockHash + ) public payable override returns (uint64 assignedSequenceNumber) { + EntropyStructs.Request storage req = requestHelper( + provider, + userCommitment, + useBlockHash, + false + ); + assignedSequenceNumber = req.sequenceNumber; + emit Requested(req); + } + + function requestWithCallback( + address provider, + bytes32 userRandomNumber + ) public payable override returns (uint64) { + EntropyStructs.Request storage req = requestHelper( + provider, + constructUserCommitment(userRandomNumber), + false, + true + ); + + emit RequestedWithCallback( + provider, + req.requester, + req.sequenceNumber, + userRandomNumber, + req + ); + + return req.sequenceNumber; + } + + // Not implemented + function reveal( + address provider, + uint64 sequenceNumber, + bytes32 userRevelation, + bytes32 providerRevelation + ) public override returns (bytes32 randomNumber) { + revert NotImplemented(); + } + + function triggerReveal( + address provider, + uint64 sequenceNumber, + bytes32 userRevelation, + bytes32 providerRevelation, + uint256 _randomNumber + ) public returns (bytes32 randomNumber) { + EntropyStructs.Request storage req = findActiveRequest( + provider, + sequenceNumber + ); + + if (req.isRequestWithCallback) { + revert EntropyErrors.InvalidRevealCall(); } - function requestWithCallback( - address provider, - bytes32 userRandomNumber - ) external payable isProvider(provider) returns (uint64) { - if (msg.value < fee) { - revert EntropyErrors.InsufficientFee(); - } - callbackRequests[currentSequenceNumber] = msg.sender; - return currentSequenceNumber++; + if (req.requester != msg.sender) { + revert EntropyErrors.Unauthorized(); } + bytes32 blockHash = blockhash(req.blockNumber); + randomNumber = bytes32(_randomNumber); + + emit Revealed( + req, + userRevelation, + providerRevelation, + blockHash, + randomNumber + ); + clearRequest(provider, sequenceNumber); + } - function triggerCallback( - uint256 randomNumber, - uint64 sequenceNumber - ) external { - address callbackAddress = callbackRequests[sequenceNumber]; - if (callbackAddress == address(0)) { - revert EntropyErrors.NoSuchRequest(); - } - bytes32 randomNumberBytes = bytes32(randomNumber); - IEntropyConsumer(callbackAddress)._entropyCallback( - sequenceNumber, - address(this), - randomNumberBytes - ); - callbackRequests[sequenceNumber] = address(0); + // Not implemented + function revealWithCallback( + address provider, + uint64 sequenceNumber, + bytes32 userRandomNumber, + bytes32 providerRevelation + ) public override { + revert NotImplemented(); + } + + function triggerRevealWithCallback( + address provider, + uint64 sequenceNumber, + bytes32 userRandomNumber, + bytes32 providerRevelation, + uint256 _randomNumber + ) public { + EntropyStructs.Request storage req = findActiveRequest( + provider, + sequenceNumber + ); + + if (!req.isRequestWithCallback) { + revert EntropyErrors.InvalidRevealCall(); } - modifier isProvider(address provider) { - if (provider != address(this)) { - revert EntropyErrors.NoSuchProvider(); - } - _; + address callAddress = req.requester; + bytes32 randomNumber = bytes32(_randomNumber); + emit RevealedWithCallback( + req, + userRandomNumber, + providerRevelation, + randomNumber + ); + + clearRequest(provider, sequenceNumber); + + uint len; + assembly { + len := extcodesize(callAddress) } + if (len != 0) { + IEntropyConsumer(callAddress)._entropyCallback( + sequenceNumber, + provider, + randomNumber + ); + } + } + + function getProviderInfo( + address provider + ) public view override returns (EntropyStructs.ProviderInfo memory info) { + info = _state.providers[provider]; + } + + function getDefaultProvider() + public + view + override + returns (address provider) + { + provider = _state.defaultProvider; + } + + function getRequest( + address provider, + uint64 sequenceNumber + ) public view override returns (EntropyStructs.Request memory req) { + req = findRequest(provider, sequenceNumber); + } + + function getFee( + address provider + ) public view override returns (uint128 feeAmount) { + return _state.providers[provider].feeInWei + _state.pythFeeInWei; + } + + function getPythFee() public view returns (uint128 feeAmount) { + return _state.pythFeeInWei; + } + + function getAccruedPythFees() + public + view + override + returns (uint128 accruedPythFeesInWei) + { + return _state.accruedPythFeesInWei; + } + + function setProviderFee(uint128 newFeeInWei) external override { + EntropyStructs.ProviderInfo storage provider = _state.providers[ + getDefaultProvider() + ]; + + if (provider.sequenceNumber == 0) { + revert EntropyErrors.NoSuchProvider(); + } + uint128 oldFeeInWei = provider.feeInWei; + provider.feeInWei = newFeeInWei; + emit ProviderFeeUpdated(getDefaultProvider(), oldFeeInWei, newFeeInWei); + } + + function setProviderFeeAsFeeManager( + address provider, + uint128 newFeeInWei + ) external override { + EntropyStructs.ProviderInfo storage providerInfo = _state.providers[ + provider + ]; + + if (providerInfo.sequenceNumber == 0) { + revert EntropyErrors.NoSuchProvider(); + } + + if (providerInfo.feeManager != msg.sender) { + revert EntropyErrors.Unauthorized(); + } + + uint128 oldFeeInWei = providerInfo.feeInWei; + providerInfo.feeInWei = newFeeInWei; + + emit ProviderFeeUpdated(provider, oldFeeInWei, newFeeInWei); + } + + function setProviderUri(bytes calldata newUri) external override { + EntropyStructs.ProviderInfo storage provider = _state.providers[ + getDefaultProvider() + ]; + if (provider.sequenceNumber == 0) { + revert EntropyErrors.NoSuchProvider(); + } + bytes memory oldUri = provider.uri; + provider.uri = newUri; + emit ProviderUriUpdated(getDefaultProvider(), oldUri, newUri); + } + + function setFeeManager(address manager) external override { + EntropyStructs.ProviderInfo storage provider = _state.providers[ + getDefaultProvider() + ]; + if (provider.sequenceNumber == 0) { + revert EntropyErrors.NoSuchProvider(); + } + + address oldFeeManager = provider.feeManager; + provider.feeManager = manager; + emit ProviderFeeManagerUpdated( + getDefaultProvider(), + oldFeeManager, + manager + ); + } + + function combineRandomValues( + bytes32 userRandomness, + bytes32 providerRandomness, + bytes32 blockHash + ) public pure override returns (bytes32 combinedRandomness) { + combinedRandomness = keccak256( + abi.encodePacked(userRandomness, providerRandomness, blockHash) + ); + } + + function constructUserCommitment( + bytes32 userRandomness + ) public pure override returns (bytes32 userCommitment) { + userCommitment = keccak256(bytes.concat(userRandomness)); + } + + function requestKey( + address provider, + uint64 sequenceNumber + ) internal pure returns (bytes32 hash, uint8 shortHash) { + hash = keccak256(abi.encodePacked(provider, sequenceNumber)); + shortHash = uint8(hash[0] & NUM_REQUESTS_MASK); + } + + function findActiveRequest( + address provider, + uint64 sequenceNumber + ) internal view returns (EntropyStructs.Request storage req) { + req = findRequest(provider, sequenceNumber); + if ( + !isActive(req) || + req.provider != provider || + req.sequenceNumber != sequenceNumber + ) revert EntropyErrors.NoSuchRequest(); + } + + function findRequest( + address provider, + uint64 sequenceNumber + ) internal view returns (EntropyStructs.Request storage req) { + (bytes32 key, uint8 shortKey) = requestKey(provider, sequenceNumber); + + req = _state.requests[shortKey]; + if (req.provider == provider && req.sequenceNumber == sequenceNumber) { + return req; + } else { + req = _state.requestsOverflow[key]; + } + } + + function clearRequest(address provider, uint64 sequenceNumber) internal { + (bytes32 key, uint8 shortKey) = requestKey(provider, sequenceNumber); + + EntropyStructs.Request storage req = _state.requests[shortKey]; + if (req.provider == provider && req.sequenceNumber == sequenceNumber) { + req.sequenceNumber = 0; + } else { + delete _state.requestsOverflow[key]; + } + } + + function allocRequest( + address provider, + uint64 sequenceNumber + ) internal returns (EntropyStructs.Request storage req) { + (, uint8 shortKey) = requestKey(provider, sequenceNumber); + + req = _state.requests[shortKey]; + if (isActive(req)) { + (bytes32 reqKey, ) = requestKey(req.provider, req.sequenceNumber); + _state.requestsOverflow[reqKey] = req; + } + } + + function isActive( + EntropyStructs.Request storage req + ) internal view returns (bool) { + return req.sequenceNumber != 0; + } } diff --git a/target_chains/ethereum/entropy_sdk/solidity/MockEntropyState.sol b/target_chains/ethereum/entropy_sdk/solidity/MockEntropyState.sol new file mode 100644 index 0000000000..b4dfac0801 --- /dev/null +++ b/target_chains/ethereum/entropy_sdk/solidity/MockEntropyState.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.0; + +import "./EntropyStructs.sol"; + +contract EntropyInternalStructs { + struct State { + address admin; + uint128 pythFeeInWei; + uint128 accruedPythFeesInWei; + address defaultProvider; + EntropyStructs.Request[32] requests; + mapping(bytes32 => EntropyStructs.Request) requestsOverflow; + mapping(address => EntropyStructs.ProviderInfo) providers; + address proposedAdmin; + } +} + +contract MockEntropyState { + uint8 public constant NUM_REQUESTS = 32; + bytes1 public constant NUM_REQUESTS_MASK = 0x1f; + EntropyInternalStructs.State _state; +}