[entropy] Add a default provider (#1150)

* making a mess here

* add default provider

* cleanup

* test new abi

* add uri to fortuna

* pr comments
This commit is contained in:
Jayant Krishnamurthy 2023-11-27 18:40:36 -08:00 committed by GitHub
parent 7641f7eacf
commit a5646bf840
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 625 additions and 468 deletions

2
fortuna/Cargo.lock generated
View File

@ -1486,7 +1486,7 @@ dependencies = [
[[package]]
name = "fortuna"
version = "1.1.0"
version = "2.0.0"
dependencies = [
"anyhow",
"axum",

File diff suppressed because it is too large Load Diff

View File

@ -55,6 +55,7 @@ pub async fn register_provider(opts: &RegisterProviderOptions) -> Result<()> {
commitment,
bincode::serialize(&commitment_metadata)?.into(),
commitment_length,
bincode::serialize(&opts.uri)?.into(),
)
.send()
.await?

View File

@ -36,4 +36,10 @@ pub struct RegisterProviderOptions {
#[arg(long = "pyth-contract-fee")]
#[arg(default_value = "100")]
pub fee: U256,
/// The URI where clients can retrieve random values from this provider,
/// i.e., wherever fortuna for this provider will be hosted.
#[arg(long = "uri")]
#[arg(default_value = "")]
pub uri: String,
}

View File

@ -72,19 +72,12 @@ import "./EntropyState.sol";
// protocol prior to the random number being revealed (i.e., prior to step (6) above). Integrators should ensure that
// the user is always incentivized to reveal their random number, and that the protocol has an escape hatch for
// cases where the user chooses not to reveal.
//
// TODOs:
// - governance??
// - correct method access modifiers (public vs external)
// - gas optimizations
// - function to check invariants??
// - need to increment pyth fees if someone transfers funds to the contract via another method
// - off-chain data ERC support?
contract Entropy is IEntropy, EntropyState {
// TODO: Use an upgradeable proxy
constructor(uint pythFeeInWei) {
constructor(uint pythFeeInWei, address defaultProvider) {
_state.accruedPythFeesInWei = 0;
_state.pythFeeInWei = pythFeeInWei;
_state.defaultProvider = defaultProvider;
}
// Register msg.sender as a randomness provider. The arguments are the provider's configuration parameters
@ -96,7 +89,8 @@ contract Entropy is IEntropy, EntropyState {
uint feeInWei,
bytes32 commitment,
bytes calldata commitmentMetadata,
uint64 chainLength
uint64 chainLength,
bytes calldata uri
) public override {
if (chainLength == 0) revert EntropyErrors.AssertionFailure();
@ -117,6 +111,7 @@ contract Entropy is IEntropy, EntropyState {
provider.currentCommitmentSequenceNumber = provider.sequenceNumber;
provider.commitmentMetadata = commitmentMetadata;
provider.endSequenceNumber = provider.sequenceNumber + chainLength;
provider.uri = uri;
provider.sequenceNumber += 1;
@ -262,6 +257,15 @@ contract Entropy is IEntropy, EntropyState {
info = _state.providers[provider];
}
function getDefaultProvider()
public
view
override
returns (address provider)
{
provider = _state.defaultProvider;
}
function getRequest(
address provider,
uint64 sequenceNumber

View File

@ -8,6 +8,7 @@ contract EntropyInternalStructs {
struct State {
uint pythFeeInWei;
uint accruedPythFeesInWei;
address defaultProvider;
mapping(address => EntropyStructs.ProviderInfo) providers;
mapping(bytes32 => EntropyStructs.Request) requests;
}

View File

@ -19,10 +19,13 @@ contract EntropyTest is Test {
bytes32[] provider1Proofs;
uint provider1FeeInWei = 8;
uint64 provider1ChainLength = 100;
bytes provider1Uri = bytes("https://foo.com");
bytes provider1CommitmentMetadata = hex"0100";
address public provider2 = address(2);
bytes32[] provider2Proofs;
uint provider2FeeInWei = 20;
bytes provider2Uri = bytes("https://bar.com");
address public user1 = address(3);
address public user2 = address(4);
@ -32,7 +35,7 @@ contract EntropyTest is Test {
bytes32 ALL_ZEROS = bytes32(uint256(0));
function setUp() public {
random = new Entropy(pythFeeInWei);
random = new Entropy(pythFeeInWei, provider1);
bytes32[] memory hashChain1 = generateHashChain(
provider1,
@ -44,14 +47,21 @@ contract EntropyTest is Test {
random.register(
provider1FeeInWei,
provider1Proofs[0],
hex"0100",
provider1ChainLength
provider1CommitmentMetadata,
provider1ChainLength,
provider1Uri
);
bytes32[] memory hashChain2 = generateHashChain(provider2, 0, 100);
provider2Proofs = hashChain2;
vm.prank(provider2);
random.register(provider2FeeInWei, provider2Proofs[0], hex"0200", 100);
random.register(
provider2FeeInWei,
provider2Proofs[0],
hex"0200",
100,
provider2Uri
);
}
function generateHashChain(
@ -59,7 +69,9 @@ contract EntropyTest is Test {
uint64 startSequenceNumber,
uint64 size
) public pure returns (bytes32[] memory hashChain) {
bytes32 initialValue = keccak256(abi.encodePacked(startSequenceNumber));
bytes32 initialValue = keccak256(
abi.encodePacked(provider, startSequenceNumber)
);
hashChain = new bytes32[](size);
for (uint64 i = 0; i < size; i++) {
hashChain[size - (i + 1)] = initialValue;
@ -212,6 +224,31 @@ contract EntropyTest is Test {
);
}
function testDefaultProvider() public {
uint64 sequenceNumber = request(
user2,
random.getDefaultProvider(),
42,
false
);
assertEq(random.getRequest(provider1, sequenceNumber).blockNumber, 0);
assertRevealReverts(
random.getDefaultProvider(),
sequenceNumber,
42,
provider2Proofs[sequenceNumber]
);
assertRevealSucceeds(
random.getDefaultProvider(),
sequenceNumber,
42,
provider1Proofs[sequenceNumber],
ALL_ZEROS
);
}
function testNoSuchProvider() public {
assertRequestReverts(10000000, unregisteredProvider, 42, false);
}
@ -318,7 +355,13 @@ contract EntropyTest is Test {
10
);
vm.prank(provider1);
random.register(provider1FeeInWei, newHashChain[0], hex"0100", 10);
random.register(
provider1FeeInWei,
newHashChain[0],
hex"0100",
10,
provider1Uri
);
assertInvariants();
EntropyStructs.ProviderInfo memory info1 = random.getProviderInfo(
provider1
@ -387,7 +430,13 @@ contract EntropyTest is Test {
// Check that overflowing the fee arithmetic causes the transaction to revert.
vm.prank(provider1);
random.register(MAX_UINT256, provider1Proofs[0], hex"0100", 100);
random.register(
MAX_UINT256,
provider1Proofs[0],
hex"0100",
100,
provider1Uri
);
vm.expectRevert();
random.getFee(provider1);
}
@ -437,7 +486,13 @@ contract EntropyTest is Test {
// Reregistering updates the required fees
vm.prank(provider1);
random.register(12345, provider1Proofs[0], hex"0100", 100);
random.register(
12345,
provider1Proofs[0],
hex"0100",
100,
provider1Uri
);
assertRequestReverts(pythFeeInWei + 12345 - 1, provider1, 42, false);
requestWithFee(user2, pythFeeInWei + 12345, provider1, 42, false);
@ -466,4 +521,13 @@ contract EntropyTest is Test {
vm.expectRevert();
random.withdraw(providerOneBalance);
}
function testGetProviderInfo() public {
EntropyStructs.ProviderInfo memory providerInfo1 = random
.getProviderInfo(provider1);
// These two fields aren't used by the Entropy contract itself -- they're just convenient info to store
// on-chain -- so they aren't tested in the other tests.
assertEq(providerInfo1.uri, provider1Uri);
assertEq(providerInfo1.commitmentMetadata, provider1CommitmentMetadata);
}
}

View File

@ -21,6 +21,10 @@ contract EntropyStructs {
// Metadata for the current commitment. Providers may optionally use this field to to help
// manage rotations (i.e., to pick the sequence number from the correct hash chain).
bytes commitmentMetadata;
// Optional URI where clients can retrieve revelations for the provider.
// Client SDKs can use this field to automatically determine how to retrieve random values for each provider.
// TODO: specify the API that must be implemented at this URI
bytes uri;
// The first sequence number that is *not* included in the current commitment (i.e., an exclusive end index).
// The contract maintains the invariant that sequenceNumber <= endSequenceNumber.
// If sequenceNumber == endSequenceNumber, the provider must rotate their commitment to add additional random values.

View File

@ -13,7 +13,8 @@ interface IEntropy is EntropyEvents {
uint feeInWei,
bytes32 commitment,
bytes calldata commitmentMetadata,
uint64 chainLength
uint64 chainLength,
bytes calldata uri
) external;
// Withdraw a portion of the accumulated fees for the provider msg.sender.
@ -55,6 +56,8 @@ interface IEntropy is EntropyEvents {
address provider
) external view returns (EntropyStructs.ProviderInfo memory info);
function getDefaultProvider() external view returns (address provider);
function getRequest(
address provider,
uint64 sequenceNumber