feat(entropy-v2): request with callback (#1342)
* request with callback * address comments * pre-commit * compilation successful * pre-commit * add tests * generate-abis * pre-commit * correct version * address comments * pre-commit * remove unused * add comments * pre-commit * gen abi * naming consistency * remove gas limit comment * requestWithCallback comment * remove unnecessary asserts * pre commit * update request with callback coment * abis regen * refactor as per feedback * abi gen * rename * implement ientropyconsumer * gen abi * comment entropy consumer * test fix * add comment * reintroduce blockhash * add error for invalid reveal call * use getEntropy in entropy consumer * add test for requestAndRevealWithCallback * pass through for entropy consumer * pre commit fix * abi gen * address comments * address feedback * gen abis * pre commit run
This commit is contained in:
parent
926aa55f75
commit
e7bf47a18e
|
@ -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";
|
||||
|
||||
|
@ -163,6 +164,54 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|||
require(sent, "withdrawal to msg.sender failed");
|
||||
}
|
||||
|
||||
// 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,
|
||||
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
|
||||
uint64 assignedSequenceNumber = providerInfo.sequenceNumber;
|
||||
if (assignedSequenceNumber >= providerInfo.endSequenceNumber)
|
||||
revert EntropyErrors.OutOfRandomness();
|
||||
providerInfo.sequenceNumber += 1;
|
||||
|
||||
// Check that fees were paid and increment the pyth / provider balances.
|
||||
uint128 requiredFee = getFee(provider);
|
||||
if (msg.value < requiredFee) revert EntropyErrors.InsufficientFee();
|
||||
providerInfo.accruedFeesInWei += providerInfo.feeInWei;
|
||||
_state.accruedPythFeesInWei += (SafeCast.toUint128(msg.value) -
|
||||
providerInfo.feeInWei);
|
||||
|
||||
// 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.
|
||||
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;
|
||||
}
|
||||
|
||||
// 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.)
|
||||
|
@ -178,89 +227,68 @@ abstract contract Entropy is IEntropy, EntropyState {
|
|||
bytes32 userCommitment,
|
||||
bool useBlockHash
|
||||
) public payable override returns (uint64 assignedSequenceNumber) {
|
||||
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;
|
||||
if (assignedSequenceNumber >= providerInfo.endSequenceNumber)
|
||||
revert EntropyErrors.OutOfRandomness();
|
||||
providerInfo.sequenceNumber += 1;
|
||||
|
||||
// Check that fees were paid and increment the pyth / provider balances.
|
||||
uint128 requiredFee = getFee(provider);
|
||||
if (msg.value < requiredFee) revert EntropyErrors.InsufficientFee();
|
||||
providerInfo.accruedFeesInWei += providerInfo.feeInWei;
|
||||
_state.accruedPythFeesInWei += (SafeCast.toUint128(msg.value) -
|
||||
providerInfo.feeInWei);
|
||||
|
||||
// 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(
|
||||
EntropyStructs.Request storage req = requestHelper(
|
||||
provider,
|
||||
assignedSequenceNumber
|
||||
userCommitment,
|
||||
useBlockHash,
|
||||
false
|
||||
);
|
||||
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;
|
||||
|
||||
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
|
||||
);
|
||||
// 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();
|
||||
emit RequestedWithCallback(
|
||||
provider,
|
||||
req.requester,
|
||||
req.sequenceNumber,
|
||||
userRandomNumber,
|
||||
req
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
EntropyStructs.ProviderInfo storage providerInfo = _state.providers[
|
||||
provider
|
||||
];
|
||||
if (providerInfo.currentCommitmentSequenceNumber < sequenceNumber) {
|
||||
providerInfo.currentCommitmentSequenceNumber = sequenceNumber;
|
||||
providerInfo.currentCommitment = providerRevelation;
|
||||
}
|
||||
IEntropyConsumer(callAddress)._entropyCallback(
|
||||
sequenceNumber,
|
||||
randomNumber
|
||||
);
|
||||
}
|
||||
|
||||
function getProviderInfo(
|
||||
|
@ -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).
|
||||
|
|
|
@ -105,6 +105,6 @@ contract EntropyUpgradable is
|
|||
}
|
||||
|
||||
function version() public pure returns (string memory) {
|
||||
return "0.1.0";
|
||||
return "0.2.0";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,16 @@ pragma solidity ^0.8.0;
|
|||
|
||||
import "forge-std/Test.sol";
|
||||
import "@pythnetwork/entropy-sdk-solidity/EntropyStructs.sol";
|
||||
import "@pythnetwork/entropy-sdk-solidity/EntropyEvents.sol";
|
||||
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
|
||||
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
|
||||
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import "./utils/EntropyTestUtils.t.sol";
|
||||
import "../contracts/entropy/EntropyUpgradable.sol";
|
||||
|
||||
// TODO
|
||||
// - fuzz test?
|
||||
contract EntropyTest is Test, EntropyTestUtils {
|
||||
contract EntropyTest is Test, EntropyTestUtils, EntropyEvents {
|
||||
ERC1967Proxy public proxy;
|
||||
EntropyUpgradable public random;
|
||||
|
||||
|
@ -222,6 +225,12 @@ contract EntropyTest is Test, EntropyTestUtils {
|
|||
ALL_ZEROS
|
||||
);
|
||||
|
||||
EntropyStructs.Request memory reqAfterReveal = random.getRequest(
|
||||
provider1,
|
||||
sequenceNumber
|
||||
);
|
||||
assertEq(reqAfterReveal.sequenceNumber, 0);
|
||||
|
||||
// You can only reveal the random number once. This isn't a feature of the contract per se, but it is
|
||||
// the expected behavior.
|
||||
assertRevealReverts(
|
||||
|
@ -729,4 +738,205 @@ contract EntropyTest is Test, EntropyTestUtils {
|
|||
vm.expectRevert();
|
||||
random.setProviderUri(newUri);
|
||||
}
|
||||
|
||||
function testRequestWithCallbackAndReveal() public {
|
||||
bytes32 userRandomNumber = bytes32(uint(42));
|
||||
uint fee = random.getFee(provider1);
|
||||
EntropyStructs.ProviderInfo memory providerInfo = random
|
||||
.getProviderInfo(provider1);
|
||||
|
||||
vm.roll(1234);
|
||||
vm.deal(user1, fee);
|
||||
vm.startPrank(user1);
|
||||
vm.expectEmit(false, false, false, true, address(random));
|
||||
emit RequestedWithCallback(
|
||||
provider1,
|
||||
user1,
|
||||
providerInfo.sequenceNumber,
|
||||
userRandomNumber,
|
||||
EntropyStructs.Request({
|
||||
provider: provider1,
|
||||
sequenceNumber: providerInfo.sequenceNumber,
|
||||
numHashes: SafeCast.toUint32(
|
||||
providerInfo.sequenceNumber -
|
||||
providerInfo.currentCommitmentSequenceNumber
|
||||
),
|
||||
commitment: keccak256(
|
||||
bytes.concat(
|
||||
random.constructUserCommitment(userRandomNumber),
|
||||
providerInfo.currentCommitment
|
||||
)
|
||||
),
|
||||
blockNumber: 1234,
|
||||
requester: user1,
|
||||
useBlockhash: false,
|
||||
isRequestWithCallback: true
|
||||
})
|
||||
);
|
||||
vm.roll(1234);
|
||||
uint64 assignedSequenceNumber = random.requestWithCallback{value: fee}(
|
||||
provider1,
|
||||
userRandomNumber
|
||||
);
|
||||
|
||||
assertEq(
|
||||
random.getRequest(provider1, assignedSequenceNumber).requester,
|
||||
user1
|
||||
);
|
||||
|
||||
assertEq(
|
||||
random.getRequest(provider1, assignedSequenceNumber).provider,
|
||||
provider1
|
||||
);
|
||||
|
||||
vm.expectRevert(EntropyErrors.InvalidRevealCall.selector);
|
||||
random.reveal(
|
||||
provider1,
|
||||
assignedSequenceNumber,
|
||||
userRandomNumber,
|
||||
provider1Proofs[assignedSequenceNumber]
|
||||
);
|
||||
vm.stopPrank();
|
||||
}
|
||||
|
||||
function testRequestWithCallbackAndRevealWithCallback() public {
|
||||
bytes32 userRandomNumber = bytes32(uint(42));
|
||||
uint fee = random.getFee(provider1);
|
||||
EntropyConsumer consumer = new EntropyConsumer(address(random));
|
||||
vm.deal(user1, fee);
|
||||
vm.prank(user1);
|
||||
uint64 assignedSequenceNumber = consumer.requestEntropy{value: fee}(
|
||||
userRandomNumber
|
||||
);
|
||||
EntropyStructs.Request memory req = random.getRequest(
|
||||
provider1,
|
||||
assignedSequenceNumber
|
||||
);
|
||||
bytes32 blockHash = bytes32(uint256(0));
|
||||
|
||||
vm.expectEmit(false, false, false, true, address(random));
|
||||
emit RevealedWithCallback(
|
||||
req,
|
||||
userRandomNumber,
|
||||
provider1Proofs[assignedSequenceNumber],
|
||||
random.combineRandomValues(
|
||||
userRandomNumber,
|
||||
provider1Proofs[assignedSequenceNumber],
|
||||
0
|
||||
)
|
||||
);
|
||||
vm.prank(user1);
|
||||
random.revealWithCallback(
|
||||
provider1,
|
||||
assignedSequenceNumber,
|
||||
userRandomNumber,
|
||||
provider1Proofs[assignedSequenceNumber]
|
||||
);
|
||||
|
||||
assertEq(consumer.sequence(), assignedSequenceNumber);
|
||||
assertEq(
|
||||
consumer.randomness(),
|
||||
random.combineRandomValues(
|
||||
userRandomNumber,
|
||||
provider1Proofs[assignedSequenceNumber],
|
||||
// No blockhash is being used in callback method. As it
|
||||
// is being depreceated. Passing 0 for it.
|
||||
0
|
||||
)
|
||||
);
|
||||
|
||||
EntropyStructs.Request memory reqAfterReveal = random.getRequest(
|
||||
provider1,
|
||||
assignedSequenceNumber
|
||||
);
|
||||
assertEq(reqAfterReveal.sequenceNumber, 0);
|
||||
}
|
||||
|
||||
function testRequestAndRevealWithCallback() public {
|
||||
uint64 sequenceNumber = request(user2, provider1, 42, false);
|
||||
assertEq(random.getRequest(provider1, sequenceNumber).requester, user2);
|
||||
|
||||
vm.expectRevert(EntropyErrors.InvalidRevealCall.selector);
|
||||
vm.prank(user2);
|
||||
random.revealWithCallback(
|
||||
provider1,
|
||||
sequenceNumber,
|
||||
bytes32(uint256(42)),
|
||||
provider1Proofs[sequenceNumber]
|
||||
);
|
||||
}
|
||||
|
||||
function testRequestWithCallbackAndRevealWithCallbackFailing() public {
|
||||
bytes32 userRandomNumber = bytes32(uint(42));
|
||||
uint fee = random.getFee(provider1);
|
||||
EntropyConsumerFails consumer = new EntropyConsumerFails(
|
||||
address(random)
|
||||
);
|
||||
vm.deal(address(consumer), fee);
|
||||
vm.startPrank(address(consumer));
|
||||
uint64 assignedSequenceNumber = random.requestWithCallback{value: fee}(
|
||||
provider1,
|
||||
userRandomNumber
|
||||
);
|
||||
|
||||
vm.expectRevert();
|
||||
random.revealWithCallback(
|
||||
provider1,
|
||||
assignedSequenceNumber,
|
||||
userRandomNumber,
|
||||
provider1Proofs[assignedSequenceNumber]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
contract EntropyConsumer is IEntropyConsumer {
|
||||
uint64 public sequence;
|
||||
bytes32 public randomness;
|
||||
address public entropy;
|
||||
|
||||
constructor(address _entropy) {
|
||||
entropy = _entropy;
|
||||
}
|
||||
|
||||
function requestEntropy(
|
||||
bytes32 randomNumber
|
||||
) public payable returns (uint64 sequenceNumber) {
|
||||
address provider = IEntropy(entropy).getDefaultProvider();
|
||||
sequenceNumber = IEntropy(entropy).requestWithCallback{
|
||||
value: msg.value
|
||||
}(provider, randomNumber);
|
||||
}
|
||||
|
||||
function getEntropy() internal view override returns (address) {
|
||||
return entropy;
|
||||
}
|
||||
|
||||
function entropyCallback(
|
||||
uint64 _sequence,
|
||||
bytes32 _randomness
|
||||
) internal override {
|
||||
sequence = _sequence;
|
||||
randomness = _randomness;
|
||||
}
|
||||
}
|
||||
|
||||
contract EntropyConsumerFails is IEntropyConsumer {
|
||||
uint64 public sequence;
|
||||
bytes32 public randomness;
|
||||
address public entropy;
|
||||
|
||||
constructor(address _entropy) {
|
||||
entropy = _entropy;
|
||||
}
|
||||
|
||||
function getEntropy() internal view override returns (address) {
|
||||
return entropy;
|
||||
}
|
||||
|
||||
function entropyCallback(
|
||||
uint64 _sequence,
|
||||
bytes32 _randomness
|
||||
) internal override {
|
||||
revert("Callback failed");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,4 +34,8 @@ library EntropyErrors {
|
|||
// The blockhash is 0.
|
||||
// Signature: 0x92555c0e
|
||||
error BlockhashUnavailable();
|
||||
// if a request was made using `requestWithCallback`, request should be fulfilled using `revealWithCallback`
|
||||
// else if a request was made using `request`, request should be fulfilled using `reveal`
|
||||
// Signature: 0x50f0dc92
|
||||
error InvalidRevealCall();
|
||||
}
|
||||
|
|
|
@ -7,6 +7,13 @@ interface EntropyEvents {
|
|||
event Registered(EntropyStructs.ProviderInfo provider);
|
||||
|
||||
event Requested(EntropyStructs.Request request);
|
||||
event RequestedWithCallback(
|
||||
address indexed provider,
|
||||
address indexed requestor,
|
||||
uint64 indexed sequenceNumber,
|
||||
bytes32 userRandomNumber,
|
||||
EntropyStructs.Request request
|
||||
);
|
||||
|
||||
event Revealed(
|
||||
EntropyStructs.Request request,
|
||||
|
@ -15,6 +22,12 @@ interface EntropyEvents {
|
|||
bytes32 blockHash,
|
||||
bytes32 randomNumber
|
||||
);
|
||||
event RevealedWithCallback(
|
||||
EntropyStructs.Request request,
|
||||
bytes32 userRandomNumber,
|
||||
bytes32 providerRevelation,
|
||||
bytes32 randomNumber
|
||||
);
|
||||
|
||||
event ProviderFeeUpdated(address provider, uint128 oldFee, uint128 newFee);
|
||||
|
||||
|
|
|
@ -54,6 +54,8 @@ contract EntropyStructs {
|
|||
address requester;
|
||||
// If true, incorporate the blockhash of blockNumber into the generated random value.
|
||||
bool useBlockhash;
|
||||
// There are 3 remaining bytes of free space in this slot.
|
||||
// If true, the requester will be called back with the generated random value.
|
||||
bool isRequestWithCallback;
|
||||
// There are 2 remaining bytes of free space in this slot.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,20 @@ interface IEntropy is EntropyEvents {
|
|||
bool useBlockHash
|
||||
) external payable returns (uint64 assignedSequenceNumber);
|
||||
|
||||
// Fulfill a request for a random number. This method validates the provided userRandomness and provider's proof
|
||||
// Request a random number. The method expects the provider address and a secret random number
|
||||
// in the arguments. It returns a sequence number.
|
||||
//
|
||||
// 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 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,
|
||||
bytes32 userRandomNumber
|
||||
) external payable returns (uint64 assignedSequenceNumber);
|
||||
|
||||
// Fulfill a request for a random number. This method validates the provided userRevelation 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.
|
||||
//
|
||||
|
@ -48,10 +61,26 @@ interface IEntropy is EntropyEvents {
|
|||
function reveal(
|
||||
address provider,
|
||||
uint64 sequenceNumber,
|
||||
bytes32 userRandomness,
|
||||
bytes32 userRevelation,
|
||||
bytes32 providerRevelation
|
||||
) external returns (bytes32 randomNumber);
|
||||
|
||||
// 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
|
||||
) external;
|
||||
|
||||
function getProviderInfo(
|
||||
address provider
|
||||
) external view returns (EntropyStructs.ProviderInfo memory info);
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-License-Identifier: Apache 2
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
abstract contract IEntropyConsumer {
|
||||
// This method is called by Entropy to provide the random number to the consumer.
|
||||
// It asserts that the msg.sender is the Entropy contract. It is not meant to be
|
||||
// override by the consumer.
|
||||
function _entropyCallback(uint64 sequence, bytes32 randomNumber) external {
|
||||
address entropy = getEntropy();
|
||||
require(entropy != address(0), "Entropy address not set");
|
||||
require(msg.sender == entropy, "Only Entropy can call this function");
|
||||
|
||||
entropyCallback(sequence, randomNumber);
|
||||
}
|
||||
|
||||
// getEntropy returns Entropy contract address. The method is being used to check that the
|
||||
// callback is indeed from Entropy contract. The consumer is expected to implement this method.
|
||||
// Entropy address can be found here - https://docs.pyth.network/entropy/contract-addresses
|
||||
function getEntropy() internal view virtual returns (address);
|
||||
|
||||
// This method is expected to be implemented by the consumer to handle the random number.
|
||||
// It will be called by _entropyCallback after _entropyCallback ensures that the call is
|
||||
// indeed from Entropy contract.
|
||||
function entropyCallback(
|
||||
uint64 sequence,
|
||||
bytes32 randomNumber
|
||||
) internal virtual;
|
||||
}
|
|
@ -19,6 +19,11 @@
|
|||
"name": "InsufficientFee",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "InvalidRevealCall",
|
||||
"type": "error"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "InvalidUpgradeMagic",
|
||||
|
|
|
@ -153,6 +153,11 @@
|
|||
"internalType": "bool",
|
||||
"name": "useBlockhash",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isRequestWithCallback",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"indexed": false,
|
||||
|
@ -164,6 +169,85 @@
|
|||
"name": "Requested",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "provider",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "requestor",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint64",
|
||||
"name": "sequenceNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32",
|
||||
"name": "userRandomNumber",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "provider",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "sequenceNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "numHashes",
|
||||
"type": "uint32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "commitment",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "blockNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "requester",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "useBlockhash",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isRequestWithCallback",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"indexed": false,
|
||||
"internalType": "struct EntropyStructs.Request",
|
||||
"name": "request",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"name": "RequestedWithCallback",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
|
@ -203,6 +287,11 @@
|
|||
"internalType": "bool",
|
||||
"name": "useBlockhash",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isRequestWithCallback",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"indexed": false,
|
||||
|
@ -237,5 +326,78 @@
|
|||
],
|
||||
"name": "Revealed",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "provider",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "sequenceNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "numHashes",
|
||||
"type": "uint32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "commitment",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "blockNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "requester",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "useBlockhash",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isRequestWithCallback",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"indexed": false,
|
||||
"internalType": "struct EntropyStructs.Request",
|
||||
"name": "request",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32",
|
||||
"name": "userRandomNumber",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32",
|
||||
"name": "providerRevelation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32",
|
||||
"name": "randomNumber",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "RevealedWithCallback",
|
||||
"type": "event"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -153,6 +153,11 @@
|
|||
"internalType": "bool",
|
||||
"name": "useBlockhash",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isRequestWithCallback",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"indexed": false,
|
||||
|
@ -164,6 +169,85 @@
|
|||
"name": "Requested",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "provider",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "requestor",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint64",
|
||||
"name": "sequenceNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32",
|
||||
"name": "userRandomNumber",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "provider",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "sequenceNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "numHashes",
|
||||
"type": "uint32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "commitment",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "blockNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "requester",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "useBlockhash",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isRequestWithCallback",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"indexed": false,
|
||||
"internalType": "struct EntropyStructs.Request",
|
||||
"name": "request",
|
||||
"type": "tuple"
|
||||
}
|
||||
],
|
||||
"name": "RequestedWithCallback",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
|
@ -203,6 +287,11 @@
|
|||
"internalType": "bool",
|
||||
"name": "useBlockhash",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isRequestWithCallback",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"indexed": false,
|
||||
|
@ -238,6 +327,79 @@
|
|||
"name": "Revealed",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "provider",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "sequenceNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "numHashes",
|
||||
"type": "uint32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "commitment",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "blockNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "requester",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "useBlockhash",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isRequestWithCallback",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"indexed": false,
|
||||
"internalType": "struct EntropyStructs.Request",
|
||||
"name": "request",
|
||||
"type": "tuple"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32",
|
||||
"name": "userRandomNumber",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32",
|
||||
"name": "providerRevelation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "bytes32",
|
||||
"name": "randomNumber",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "RevealedWithCallback",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
|
@ -453,6 +615,11 @@
|
|||
"internalType": "bool",
|
||||
"name": "useBlockhash",
|
||||
"type": "bool"
|
||||
},
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "isRequestWithCallback",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"internalType": "struct EntropyStructs.Request",
|
||||
|
@ -525,6 +692,30 @@
|
|||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "provider",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "userRandomNumber",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "requestWithCallback",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "assignedSequenceNumber",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"stateMutability": "payable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
|
@ -539,7 +730,7 @@
|
|||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "userRandomness",
|
||||
"name": "userRevelation",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
|
@ -559,6 +750,34 @@
|
|||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "provider",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "sequenceNumber",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "userRandomNumber",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "providerRevelation",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "revealWithCallback",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "uint64",
|
||||
"name": "sequence",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "randomNumber",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "_entropyCallback",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@pythnetwork/entropy-sdk-solidity",
|
||||
"version": "1.1.3",
|
||||
"version": "1.2.0",
|
||||
"description": "Generate secure random numbers with Pyth Entropy",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"format": "npx prettier --write .",
|
||||
"generate-abi": "npx generate-abis IEntropy EntropyErrors EntropyEvents EntropyStructs",
|
||||
"generate-abi": "npx generate-abis IEntropy IEntropyConsumer EntropyErrors EntropyEvents EntropyStructs",
|
||||
"check-abi": "git diff --exit-code abis"
|
||||
},
|
||||
"keywords": [
|
||||
|
|
Loading…
Reference in New Issue