Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
e0af292
request with callback
Mar 5, 2024
ddc87ed
address comments
Mar 6, 2024
cfc07cc
pre-commit
Mar 6, 2024
a86e9c0
compilation successful
Mar 6, 2024
8aeb984
pre-commit
Mar 6, 2024
42a32fb
add tests
Mar 7, 2024
70745ce
generate-abis
Mar 7, 2024
c5a9ec1
pre-commit
Mar 7, 2024
d1e45b3
correct version
Mar 7, 2024
65978d1
address comments
Mar 7, 2024
3f3b101
pre-commit
Mar 7, 2024
bdca251
remove unused
Mar 7, 2024
de2557f
add comments
Mar 7, 2024
e1cf25d
pre-commit
Mar 7, 2024
8354963
gen abi
Mar 7, 2024
221cdb1
naming consistency
Mar 8, 2024
fc1d251
remove gas limit comment
Mar 8, 2024
ee8bbed
requestWithCallback comment
Mar 8, 2024
3ad22a0
remove unnecessary asserts
Mar 8, 2024
1623c2d
pre commit
Mar 8, 2024
6f3ffc1
update request with callback coment
Mar 8, 2024
787d2d6
abis regen
Mar 8, 2024
02c16a2
refactor as per feedback
Mar 8, 2024
2e8a8e9
abi gen
Mar 8, 2024
532d566
rename
Mar 8, 2024
257ac25
implement ientropyconsumer
Mar 8, 2024
6c307ca
gen abi
Mar 8, 2024
16f83e7
comment entropy consumer
Mar 8, 2024
c825136
test fix
Mar 8, 2024
4ed0312
add comment
Mar 8, 2024
1935494
reintroduce blockhash
Mar 11, 2024
e190dfa
add error for invalid reveal call
Mar 11, 2024
435dc3b
use getEntropy in entropy consumer
Mar 11, 2024
69160aa
add test for requestAndRevealWithCallback
Mar 11, 2024
4a6675b
pass through for entropy consumer
Mar 11, 2024
ceae64d
pre commit fix
Mar 11, 2024
472943e
abi gen
Mar 11, 2024
8984b6a
address comments
Mar 11, 2024
f3343bf
address feedback
Mar 11, 2024
75871a0
gen abis
Mar 11, 2024
df799b1
pre commit run
Mar 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
231 changes: 179 additions & 52 deletions target_chains/ethereum/contracts/contracts/entropy/Entropy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
import "@pythnetwork/entropy-sdk-solidity/EntropyErrors.sol";
import "@pythnetwork/entropy-sdk-solidity/EntropyEvents.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";
import "./EntropyState.sol";

Expand Down Expand Up @@ -163,29 +164,23 @@ abstract contract Entropy is IEntropy, EntropyState {
require(sent, "withdrawal to msg.sender failed");
}

// As a user, request a random number from `provider`. Prior to calling this method, the user should
// generate a random number x and keep it secret. The user should then compute hash(x) and pass that
// as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
//
// This method returns a sequence number. The user should pass this sequence number to
// their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
// number. The user should then call fulfillRequest to construct the final random number.
//
// This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
// Note that excess value is *not* refunded to the caller.
function request(
// requestHelper allocates and returns a new request for the given provider.
// Note: This method will revert unless the caller provides a sufficient fee
// (at least getFee(provider)) as msg.value.
function requestHelper(
address provider,
bytes32 userCommitment,
bool useBlockHash
) public payable override returns (uint64 assignedSequenceNumber) {
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();

// Assign a sequence number to the request
assignedSequenceNumber = providerInfo.sequenceNumber;
uint64 assignedSequenceNumber = providerInfo.sequenceNumber;
if (assignedSequenceNumber >= providerInfo.endSequenceNumber)
revert EntropyErrors.OutOfRandomness();
providerInfo.sequenceNumber += 1;
Expand All @@ -200,10 +195,7 @@ abstract contract Entropy is IEntropy, EntropyState {
// Store the user's commitment so that we can fulfill the request later.
// Warning: this code needs to overwrite *every* field in the request, because the returned request can be
// filled with arbitrary data.
EntropyStructs.Request storage req = allocRequest(
provider,
assignedSequenceNumber
);
req = allocRequest(provider, assignedSequenceNumber);
req.provider = provider;
req.sequenceNumber = assignedSequenceNumber;
req.numHashes = SafeCast.toUint32(
Expand All @@ -216,51 +208,87 @@ abstract contract Entropy is IEntropy, EntropyState {
req.requester = msg.sender;

req.blockNumber = SafeCast.toUint64(block.number);
req.useBlockhash = useBlockHash;
req.useBlockhash = useBlockhash;
req.isRequestWithCallback = isRequestWithCallback;
}

// As a user, request a random number from `provider`. Prior to calling this method, the user should
// generate a random number x and keep it secret. The user should then compute hash(x) and pass that
// as the userCommitment argument. (You may call the constructUserCommitment method to compute the hash.)
//
// This method returns a sequence number. The user should pass this sequence number to
// their chosen provider (the exact method for doing so will depend on the provider) to retrieve the provider's
// number. The user should then call fulfillRequest to construct the final random number.
//
// This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
// Note that excess value is *not* refunded to the caller.
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);
}

// Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
// against the corresponding commitments in the in-flight request. If both values are validated, this function returns
// the corresponding random number.
// Request a random number. The method expects the provider address and a secret random number
// in the arguments. It returns a sequence number.
//
// Note that this function can only be called once per in-flight request. Calling this function deletes the stored
// request information (so that the contract doesn't use a linear amount of storage in the number of requests).
// If you need to use the returned random number more than once, you are responsible for storing it.
// The address calling this function should be a contract that inherits from the IEntropyConsumer interface.
// The `entropyCallback` method on that interface will receive a callback with the generated random number.
//
// This function must be called by the same `msg.sender` that originally requested the random number. This check
// prevents denial-of-service attacks where another actor front-runs the requester's reveal transaction.
function reveal(
// This method will revert unless the caller provides a sufficient fee (at least getFee(provider)) as msg.value.
// Note that excess value is *not* refunded to the caller.
function requestWithCallback(
address provider,
uint64 sequenceNumber,
bytes32 userRandomness,
bytes32 providerRevelation
) public override returns (bytes32 randomNumber) {
EntropyStructs.Request storage req = findRequest(
bytes32 userRandomNumber
) public payable override returns (uint64) {
EntropyStructs.Request storage req = requestHelper(
provider,
sequenceNumber
constructUserCommitment(userRandomNumber),
// If useBlockHash is set to true, it allows a scenario in which the provider and miner can collude.
// If we remove the blockHash from this, the provider would have no choice but to provide its committed
// random number. Hence, useBlockHash is set to false.
false,
true
);

emit RequestedWithCallback(
provider,
req.requester,
req.sequenceNumber,
userRandomNumber,
req
);
// Check that there is an active request for the given provider / sequence number.
if (
req.sequenceNumber == 0 ||
req.provider != provider ||
req.sequenceNumber != sequenceNumber
) revert EntropyErrors.NoSuchRequest();

if (req.requester != msg.sender) revert EntropyErrors.Unauthorized();
return req.sequenceNumber;
}

// This method validates the provided user's revelation and provider's revelation against the corresponding
// commitment in the in-flight request. If both values are validated, this method will update the provider
// current commitment and returns the generated random number.
function revealHelper(
EntropyStructs.Request storage req,
bytes32 userRevelation,
bytes32 providerRevelation
) internal returns (bytes32 randomNumber, bytes32 blockHash) {
bytes32 providerCommitment = constructProviderCommitment(
req.numHashes,
providerRevelation
);
bytes32 userCommitment = constructUserCommitment(userRandomness);
bytes32 userCommitment = constructUserCommitment(userRevelation);
if (
keccak256(bytes.concat(userCommitment, providerCommitment)) !=
req.commitment
) revert EntropyErrors.IncorrectRevelation();

bytes32 blockHash = bytes32(uint256(0));
blockHash = bytes32(uint256(0));
if (req.useBlockhash) {
bytes32 _blockHash = blockhash(req.blockNumber);

Expand All @@ -277,28 +305,110 @@ abstract contract Entropy is IEntropy, EntropyState {
}

randomNumber = combineRandomValues(
userRandomness,
userRevelation,
providerRevelation,
blockHash
);

EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
req.provider
];
if (providerInfo.currentCommitmentSequenceNumber < req.sequenceNumber) {
providerInfo.currentCommitmentSequenceNumber = req.sequenceNumber;
providerInfo.currentCommitment = providerRevelation;
}
}

// Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
// against the corresponding commitments in the in-flight request. If both values are validated, this function returns
// the corresponding random number.
//
// Note that this function can only be called once per in-flight request. Calling this function deletes the stored
// request information (so that the contract doesn't use a linear amount of storage in the number of requests).
// If you need to use the returned random number more than once, you are responsible for storing it.
//
// This function must be called by the same `msg.sender` that originally requested the random number. This check
// prevents denial-of-service attacks where another actor front-runs the requester's reveal transaction.
function reveal(
address provider,
uint64 sequenceNumber,
bytes32 userRevelation,
bytes32 providerRevelation
) public override returns (bytes32 randomNumber) {
EntropyStructs.Request storage req = findActiveRequest(
provider,
sequenceNumber
);

if (req.isRequestWithCallback) {
revert EntropyErrors.InvalidRevealCall();
}

if (req.requester != msg.sender) {
revert EntropyErrors.Unauthorized();
}
bytes32 blockHash;
(randomNumber, blockHash) = revealHelper(
req,
userRevelation,
providerRevelation
);
emit Revealed(
req,
userRandomness,
userRevelation,
providerRevelation,
blockHash,
randomNumber
);

clearRequest(provider, sequenceNumber);
}

EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
provider
];
if (providerInfo.currentCommitmentSequenceNumber < sequenceNumber) {
providerInfo.currentCommitmentSequenceNumber = sequenceNumber;
providerInfo.currentCommitment = providerRevelation;
// Fulfill a request for a random number and call back the requester. This method validates the provided userRandomness
// and provider's revelation against the corresponding commitment in the in-flight request. If both values are validated,
// this function calls the requester's entropyCallback method with the sequence number and the random number as arguments.
//
// Note that this function can only be called once per in-flight request. Calling this function deletes the stored
// request information (so that the contract doesn't use a linear amount of storage in the number of requests).
// If you need to use the returned random number more than once, you are responsible for storing it.
//
// Anyone can call this method to fulfill a request, but the callback will only be made to the original requester.
function revealWithCallback(
address provider,
uint64 sequenceNumber,
bytes32 userRandomNumber,
bytes32 providerRevelation
) public override {
EntropyStructs.Request storage req = findActiveRequest(
provider,
sequenceNumber
);

if (!req.isRequestWithCallback) {
revert EntropyErrors.InvalidRevealCall();
}
bytes32 blockHash;
bytes32 randomNumber;
(randomNumber, blockHash) = revealHelper(
req,
userRandomNumber,
providerRevelation
);

address callAddress = req.requester;

emit RevealedWithCallback(
req,
userRandomNumber,
providerRevelation,
randomNumber
);

clearRequest(provider, sequenceNumber);

IEntropyConsumer(callAddress)._entropyCallback(
sequenceNumber,
randomNumber
);
}

function getProviderInfo(
Expand Down Expand Up @@ -408,6 +518,23 @@ abstract contract Entropy is IEntropy, EntropyState {
}
}

// Find an in-flight active request for given the provider and the sequence number.
// This method returns a reference to the request, and will revert if the request is
// not active.
function findActiveRequest(
address provider,
uint64 sequenceNumber
) internal view returns (EntropyStructs.Request storage req) {
req = findRequest(provider, sequenceNumber);

// Check there is an active request for the given provider and sequence number.
if (
!isActive(req) ||
req.provider != provider ||
req.sequenceNumber != sequenceNumber
) revert EntropyErrors.NoSuchRequest();
}

// Find an in-flight request.
// Note that this method can return requests that are not currently active. The caller is responsible for checking
// that the returned request is active (if they care).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,6 @@ contract EntropyUpgradable is
}

function version() public pure returns (string memory) {
return "0.1.0";
return "0.2.0";
}
}
Loading