diff --git a/fortuna/config.yaml b/fortuna/config.yaml index 29f4fc0d6b..e8689be6e4 100644 --- a/fortuna/config.yaml +++ b/fortuna/config.yaml @@ -2,3 +2,9 @@ chains: optimism-goerli: geth_rpc_addr: https://goerli.optimism.io contract_addr: 0x28F16Af4D87523910b843a801454AEde5F9B0459 + avalanche-fuji: + geth_rpc_addr: https://api.avax-test.network/ext/bc/C/rpc + contract_addr: 0xD42c7a708E74AD19401D907a14146F006c851Ee3 + eos-evm-testnet: + geth_rpc_addr: https://api.testnet.evm.eosnetwork.com/ + contract_addr: 0xD42c7a708E74AD19401D907a14146F006c851Ee3 diff --git a/fortuna/src/main.rs b/fortuna/src/main.rs index fb2180b4d4..f06fbd637b 100644 --- a/fortuna/src/main.rs +++ b/fortuna/src/main.rs @@ -16,7 +16,7 @@ pub mod state; // Server TODO list: // - Tests // - Reduce memory requirements for storing hash chains to increase scalability -// - Name things nicely (service name, API resource names) +// - Name things nicely (API resource names) // - README // - Choose data formats for binary data #[tokio::main] diff --git a/package-lock.json b/package-lock.json index 160dd382b3..0f4f6efa06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "target_chains/cosmwasm/tools", "target_chains/cosmwasm/deploy-scripts", "target_chains/ethereum/contracts", + "target_chains/ethereum/entropy_sdk/solidity", "target_chains/ethereum/sdk/js", "target_chains/ethereum/sdk/solidity", "target_chains/ethereum/examples/oracle_swap/app", @@ -11755,6 +11756,10 @@ "resolved": "target_chains/cosmwasm/tools", "link": true }, + "node_modules/@pythnetwork/entropy-sdk-solidity": { + "resolved": "target_chains/ethereum/entropy_sdk/solidity", + "link": true + }, "node_modules/@pythnetwork/eth-oracle-swap-example-frontend": { "resolved": "target_chains/ethereum/examples/oracle_swap/app", "link": true @@ -57381,6 +57386,7 @@ "@openzeppelin/contracts": "^4.5.0", "@openzeppelin/contracts-upgradeable": "^4.5.2", "@openzeppelin/hardhat-upgrades": "^1.22.1", + "@pythnetwork/entropy-sdk-solidity": "*", "@pythnetwork/pyth-multisig-wh-message-builder": "*", "@pythnetwork/pyth-sdk-solidity": "^2.2.0", "contract_manager": "*", @@ -57988,6 +57994,54 @@ } } }, + "target_chains/ethereum/entropy_sdk/solidity": { + "version": "0.1.0", + "license": "Apache-2.0", + "devDependencies": { + "prettier": "^2.7.1", + "prettier-plugin-solidity": "^1.0.0-rc.1", + "solc": "^0.8.15" + } + }, + "target_chains/ethereum/entropy_sdk/solidity/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "target_chains/ethereum/entropy_sdk/solidity/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "target_chains/ethereum/entropy_sdk/solidity/node_modules/solc": { + "version": "0.8.21", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.21.tgz", + "integrity": "sha512-N55ogy2dkTRwiONbj4e6wMZqUNaLZkiRcjGyeafjLYzo/tf/IvhHY5P5wpe+H3Fubh9idu071i8eOGO31s1ylg==", + "dev": true, + "dependencies": { + "command-exists": "^1.2.8", + "commander": "^8.1.0", + "follow-redirects": "^1.12.1", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "semver": "^5.5.0", + "tmp": "0.0.33" + }, + "bin": { + "solcjs": "solc.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, "target_chains/ethereum/examples/oracle_swap/app": { "name": "@pythnetwork/eth-oracle-swap-example-frontend", "version": "0.1.0", @@ -66350,6 +66404,43 @@ } } }, + "@pythnetwork/entropy-sdk-solidity": { + "version": "file:target_chains/ethereum/entropy_sdk/solidity", + "requires": { + "prettier": "^2.7.1", + "prettier-plugin-solidity": "^1.0.0-rc.1", + "solc": "^0.8.15" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "dev": true + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + }, + "solc": { + "version": "0.8.21", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.8.21.tgz", + "integrity": "sha512-N55ogy2dkTRwiONbj4e6wMZqUNaLZkiRcjGyeafjLYzo/tf/IvhHY5P5wpe+H3Fubh9idu071i8eOGO31s1ylg==", + "dev": true, + "requires": { + "command-exists": "^1.2.8", + "commander": "^8.1.0", + "follow-redirects": "^1.12.1", + "js-sha3": "0.8.0", + "memorystream": "^0.3.1", + "semver": "^5.5.0", + "tmp": "0.0.33" + } + } + } + }, "@pythnetwork/eth-oracle-swap-example-frontend": { "version": "file:target_chains/ethereum/examples/oracle_swap/app", "requires": { @@ -67858,6 +67949,7 @@ "@openzeppelin/hardhat-upgrades": "^1.22.1", "@openzeppelin/test-helpers": "^0.5.15", "@openzeppelin/truffle-upgrades": "^1.14.0", + "@pythnetwork/entropy-sdk-solidity": "*", "@pythnetwork/pyth-multisig-wh-message-builder": "*", "@pythnetwork/pyth-sdk-solidity": "^2.2.0", "@truffle/hdwallet-provider": "^2.1.5", diff --git a/package.json b/package.json index 8174598694..4872b6d167 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "target_chains/cosmwasm/tools", "target_chains/cosmwasm/deploy-scripts", "target_chains/ethereum/contracts", + "target_chains/ethereum/entropy_sdk/solidity", "target_chains/ethereum/sdk/js", "target_chains/ethereum/sdk/solidity", "target_chains/ethereum/examples/oracle_swap/app", diff --git a/target_chains/ethereum/contracts/contracts/random/PythRandom.sol b/target_chains/ethereum/contracts/contracts/random/PythRandom.sol index 63a164dd12..16d0b401fe 100644 --- a/target_chains/ethereum/contracts/contracts/random/PythRandom.sol +++ b/target_chains/ethereum/contracts/contracts/random/PythRandom.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.0; -import "./PythRandomState.sol"; -import "./PythRandomErrors.sol"; -import "./PythRandomEvents.sol"; +import "@pythnetwork/entropy-sdk-solidity/PythRandomState.sol"; +import "@pythnetwork/entropy-sdk-solidity/PythRandomErrors.sol"; +import "@pythnetwork/entropy-sdk-solidity/PythRandomEvents.sol"; +import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; // PythRandom implements a secure 2-party random number generation procedure. The protocol // is an extension of a simple commit/reveal protocol. The original version has the following steps: @@ -78,7 +79,7 @@ import "./PythRandomEvents.sol"; // - 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 PythRandom is PythRandomState, PythRandomEvents { +contract PythRandom is IEntropy, PythRandomState { // TODO: Use an upgradeable proxy constructor(uint pythFeeInWei) { _state.accruedPythFeesInWei = 0; @@ -95,7 +96,7 @@ contract PythRandom is PythRandomState, PythRandomEvents { bytes32 commitment, bytes32 commitmentMetadata, uint64 chainLength - ) public { + ) public override { if (chainLength == 0) revert PythRandomErrors.AssertionFailure(); PythRandomStructs.ProviderInfo storage provider = _state.providers[ @@ -124,7 +125,7 @@ contract PythRandom is PythRandomState, PythRandomEvents { // Withdraw a portion of the accumulated fees for the provider msg.sender. // Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient // balance of fees in the contract). - function withdraw(uint256 amount) public { + function withdraw(uint256 amount) public override { PythRandomStructs.ProviderInfo storage providerInfo = _state.providers[ msg.sender ]; @@ -155,7 +156,7 @@ contract PythRandom is PythRandomState, PythRandomEvents { address provider, bytes32 userCommitment, bool useBlockHash - ) public payable returns (uint64 assignedSequenceNumber) { + ) public payable override returns (uint64 assignedSequenceNumber) { PythRandomStructs.ProviderInfo storage providerInfo = _state.providers[ provider ]; @@ -205,7 +206,7 @@ contract PythRandom is PythRandomState, PythRandomEvents { uint64 sequenceNumber, bytes32 userRandomness, bytes32 providerRevelation - ) public returns (bytes32 randomNumber) { + ) public override returns (bytes32 randomNumber) { // TODO: do we need to check that this request exists? // TODO: this method may need to be authenticated to prevent griefing bytes32 key = requestKey(provider, sequenceNumber); @@ -257,25 +258,33 @@ contract PythRandom is PythRandomState, PythRandomEvents { function getProviderInfo( address provider - ) public view returns (PythRandomStructs.ProviderInfo memory info) { + ) + public + view + override + returns (PythRandomStructs.ProviderInfo memory info) + { info = _state.providers[provider]; } function getRequest( address provider, uint64 sequenceNumber - ) public view returns (PythRandomStructs.Request memory req) { + ) public view override returns (PythRandomStructs.Request memory req) { bytes32 key = requestKey(provider, sequenceNumber); req = _state.requests[key]; } - function getFee(address provider) public view returns (uint feeAmount) { + function getFee( + address provider + ) public view override returns (uint feeAmount) { return _state.providers[provider].feeInWei + _state.pythFeeInWei; } function getAccruedPythFees() public view + override returns (uint accruedPythFeesInWei) { return _state.accruedPythFeesInWei; @@ -283,7 +292,7 @@ contract PythRandom is PythRandomState, PythRandomEvents { function constructUserCommitment( bytes32 userRandomness - ) public pure returns (bytes32 userCommitment) { + ) public pure override returns (bytes32 userCommitment) { userCommitment = keccak256(bytes.concat(userRandomness)); } @@ -291,7 +300,7 @@ contract PythRandom is PythRandomState, PythRandomEvents { bytes32 userRandomness, bytes32 providerRandomness, bytes32 blockHash - ) public pure returns (bytes32 combinedRandomness) { + ) public pure override returns (bytes32 combinedRandomness) { combinedRandomness = keccak256( abi.encodePacked(userRandomness, providerRandomness, blockHash) ); diff --git a/target_chains/ethereum/contracts/package.json b/target_chains/ethereum/contracts/package.json index c82fdf5c71..1f19b54d7d 100644 --- a/target_chains/ethereum/contracts/package.json +++ b/target_chains/ethereum/contracts/package.json @@ -36,6 +36,7 @@ "@openzeppelin/hardhat-upgrades": "^1.22.1", "@pythnetwork/pyth-multisig-wh-message-builder": "*", "@pythnetwork/pyth-sdk-solidity": "^2.2.0", + "@pythnetwork/entropy-sdk-solidity": "*", "contract_manager": "*", "dotenv": "^10.0.0", "elliptic": "^6.5.2", diff --git a/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol b/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol new file mode 100644 index 0000000000..e121de3f8e --- /dev/null +++ b/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.0; + +import "./PythRandomEvents.sol"; + +interface IEntropy is PythRandomEvents { + // Register msg.sender as a randomness provider. The arguments are the provider's configuration parameters + // and initial commitment. Re-registering the same provider rotates the provider's commitment (and updates + // the feeInWei). + // + // chainLength is the number of values in the hash chain *including* the commitment, that is, chainLength >= 1. + function register( + uint feeInWei, + bytes32 commitment, + bytes32 commitmentMetadata, + uint64 chainLength + ) external; + + // Withdraw a portion of the accumulated fees for the provider msg.sender. + // Calling this function will transfer `amount` wei to the caller (provided that they have accrued a sufficient + // balance of fees in the contract). + function withdraw(uint256 amount) external; + + // 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 + ) external payable returns (uint64 assignedSequenceNumber); + + // 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. + function reveal( + address provider, + uint64 sequenceNumber, + bytes32 userRandomness, + bytes32 providerRevelation + ) external returns (bytes32 randomNumber); + + function getProviderInfo( + address provider + ) external view returns (PythRandomStructs.ProviderInfo memory info); + + function getRequest( + address provider, + uint64 sequenceNumber + ) external view returns (PythRandomStructs.Request memory req); + + function getFee(address provider) external view returns (uint feeAmount); + + function getAccruedPythFees() + external + view + returns (uint accruedPythFeesInWei); + + function constructUserCommitment( + bytes32 userRandomness + ) external pure returns (bytes32 userCommitment); + + function combineRandomValues( + bytes32 userRandomness, + bytes32 providerRandomness, + bytes32 blockHash + ) external pure returns (bytes32 combinedRandomness); +} diff --git a/target_chains/ethereum/contracts/contracts/random/PythRandomErrors.sol b/target_chains/ethereum/entropy_sdk/solidity/PythRandomErrors.sol similarity index 100% rename from target_chains/ethereum/contracts/contracts/random/PythRandomErrors.sol rename to target_chains/ethereum/entropy_sdk/solidity/PythRandomErrors.sol diff --git a/target_chains/ethereum/contracts/contracts/random/PythRandomEvents.sol b/target_chains/ethereum/entropy_sdk/solidity/PythRandomEvents.sol similarity index 100% rename from target_chains/ethereum/contracts/contracts/random/PythRandomEvents.sol rename to target_chains/ethereum/entropy_sdk/solidity/PythRandomEvents.sol diff --git a/target_chains/ethereum/contracts/contracts/random/PythRandomState.sol b/target_chains/ethereum/entropy_sdk/solidity/PythRandomState.sol similarity index 100% rename from target_chains/ethereum/contracts/contracts/random/PythRandomState.sol rename to target_chains/ethereum/entropy_sdk/solidity/PythRandomState.sol diff --git a/target_chains/ethereum/entropy_sdk/solidity/package.json b/target_chains/ethereum/entropy_sdk/solidity/package.json new file mode 100644 index 0000000000..a295184e36 --- /dev/null +++ b/target_chains/ethereum/entropy_sdk/solidity/package.json @@ -0,0 +1,29 @@ +{ + "name": "@pythnetwork/entropy-sdk-solidity", + "version": "0.1.0", + "description": "Generate secure random numbers with Pyth Entropy", + "repository": { + "type": "git", + "url": "https://github.com/pyth-network/pyth-crosschain", + "directory": "target_chains/ethereum/entropy_sdk/solidity" + }, + "scripts": { + "format": "npx prettier --write ." + }, + "keywords": [ + "pyth", + "solidity", + "random" + ], + "author": "Douro Labs", + "license": "Apache-2.0", + "bugs": { + "url": "https://github.com/pyth-network/pyth-crosschain/issues" + }, + "homepage": "https://github.com/pyth-network/pyth-crosschain/tree/main/target_chains/ethereum/entropy_sdk/solidity", + "devDependencies": { + "prettier": "^2.7.1", + "prettier-plugin-solidity": "^1.0.0-rc.1", + "solc": "^0.8.15" + } +} diff --git a/target_chains/ethereum/examples/coin_flip/contract/.gitignore b/target_chains/ethereum/examples/coin_flip/contract/.gitignore new file mode 100644 index 0000000000..e9f63b3418 --- /dev/null +++ b/target_chains/ethereum/examples/coin_flip/contract/.gitignore @@ -0,0 +1,4 @@ +lib/* +!lib/README.md +cache +out diff --git a/target_chains/ethereum/examples/coin_flip/contract/foundry.toml b/target_chains/ethereum/examples/coin_flip/contract/foundry.toml new file mode 100644 index 0000000000..db25c7154b --- /dev/null +++ b/target_chains/ethereum/examples/coin_flip/contract/foundry.toml @@ -0,0 +1,7 @@ +[profile.default] +solc = '0.8.4' +src = 'src' +out = 'out' +libs = ['lib', '../../../entropy_sdk/solidity'] + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config diff --git a/target_chains/ethereum/examples/coin_flip/contract/lib/README.md b/target_chains/ethereum/examples/coin_flip/contract/lib/README.md new file mode 100644 index 0000000000..41b1cdfa62 --- /dev/null +++ b/target_chains/ethereum/examples/coin_flip/contract/lib/README.md @@ -0,0 +1 @@ +Forge installs the dependencies in this folder. They are .gitignored diff --git a/target_chains/ethereum/examples/coin_flip/contract/remappings.txt b/target_chains/ethereum/examples/coin_flip/contract/remappings.txt new file mode 100644 index 0000000000..cb63f8ee44 --- /dev/null +++ b/target_chains/ethereum/examples/coin_flip/contract/remappings.txt @@ -0,0 +1,3 @@ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +entropy-sdk-solidity/=../../../entropy_sdk/solidity/ diff --git a/target_chains/ethereum/examples/coin_flip/contract/scripts/deploy.sh b/target_chains/ethereum/examples/coin_flip/contract/scripts/deploy.sh new file mode 100755 index 0000000000..ef0fe90e02 --- /dev/null +++ b/target_chains/ethereum/examples/coin_flip/contract/scripts/deploy.sh @@ -0,0 +1,20 @@ +#!/bin/bash -e + +# URL of the ethereum RPC node to use. Choose this based on your target network +RPC_URL=https://api.avax-test.network/ext/bc/C/rpc + +# The address of the Pyth contract on your network. See the list of contract addresses here https://docs.pyth.network/documentation/pythnet-price-feeds/evm +ENTROPY_CONTRACT_ADDRESS="0xD42c7a708E74AD19401D907a14146F006c851Ee3" +PROVIDER="0x368397bDc956b4F23847bE244f350Bde4615F25E" + +# Avalanche fuji address: +# 0x544c5ab499C38dff495724451783F63a3eeA40F2 + +# Note the -l here uses a ledger wallet to deploy your contract. You may need to change this +# option if you are using a different wallet. +forge create src/CoinFlip.sol:CoinFlip \ + -l \ + --rpc-url $RPC_URL \ + --constructor-args \ + $ENTROPY_CONTRACT_ADDRESS \ + $PROVIDER diff --git a/target_chains/ethereum/examples/coin_flip/contract/src/CoinFlip.sol b/target_chains/ethereum/examples/coin_flip/contract/src/CoinFlip.sol new file mode 100644 index 0000000000..8adb453e41 --- /dev/null +++ b/target_chains/ethereum/examples/coin_flip/contract/src/CoinFlip.sol @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: Apache 2 +pragma solidity ^0.8.0; + +import "entropy-sdk-solidity/IEntropy.sol"; + +library CoinFlipErrors { + error IncorrectSender(); + + error InsufficientFee(); +} + +/// Example contract using Pyth Entropy to allow a user to flip a secure fair coin. +/// Users interact with the contract by sending two transactions: +/// 1. Users request a coin flip. This operation commits the flip to use a specific (but currently unknown) random number +/// generated by Pyth Entropy. +/// 2. Users reveal the result of the coin flip. This operation reveals the random number from Pyth Entropy and checks +/// its validity, then converts the random number into the result of a coin flip. +contract CoinFlip { + // Event emitted when a coin flip is requested. The sequence number is required to reveal + // the result of the flip. + event FlipRequest(uint64 sequenceNumber); + + // Event emitted when the result of the coin flip is known. + event FlipResult(bool isHeads); + + // Contracts using Pyth Entropy should import the solidity SDK and then store both the Entropy contract + // and a specific entropy provider to use for requests. Each provider commits to a sequence of random numbers. + // Providers are then responsible for two things: + // 1. Operating an off-chain service that reveals their random numbers once they've been committed to on-chain + // 2. Maintaining the secrecy of the other random numbers + // Users should choose a reliable provider who they trust to uphold these commitments. + // (For the moment, the only available provider is 0x368397bDc956b4F23847bE244f350Bde4615F25E) + IEntropy private entropy; + address private entropyProvider; + + // The contract is required to maintain a collection of in-flight requests. This mapping allows the contract + // to match the revealed random numbers against the original requests. The key of the map can be the + // sequence number provided by the Entropy protocol, and the value can be whatever information the protocol + // needs to resolve in-flight requests. + mapping(uint64 => address) private requestedFlips; + + constructor(address _entropy, address _entropyProvider) { + entropy = IEntropy(_entropy); + entropyProvider = _entropyProvider; + } + + // Request to flip a coin. The caller should generate a random number prior to calling this method, then + // submit the hash of that number as userCommitment. (You can call `IEntropy.constructUserCommitment` with + // the random number to generate the commitment.) + function requestFlip(bytes32 userCommitment) external payable { + // The entropy protocol requires the caller to pay a fee (in native gas tokens) per requested random number. + // This fee can either be paid by the contract itself or passed on to the end user. + // This implementation of the requestFlip method passes on the fee to the end user. + uint256 fee = entropy.getFee(entropyProvider); + if (msg.value < fee) { + revert CoinFlipErrors.InsufficientFee(); + } + + // Request the random number from the Entropy protocol. The call returns a sequence number that uniquely + // identifies the generated random number. Callers should save this sequence number so that they can match + // which request is being revealed in the next stage of the protocol. + // + // The final `true` parameter to this method incorporates the blockhash of the request's block into the + // generated random value. The blockhash adds another level of security and manipulation-resistance to the + // random value. Set this to `true` unless your blockchain has poor support for retrieving blockhashes. + uint64 sequenceNumber = entropy.request{value: fee}( + entropyProvider, + userCommitment, + true + ); + requestedFlips[sequenceNumber] = msg.sender; + + emit FlipRequest(sequenceNumber); + } + + // Reveal the result of the coin flip. The caller must have an in-flight request for a coin flip, which is + // identified by `sequenceNumber`. The caller must additionally provide the random number that they previously + // committed to, as well as the entropy provider's random number. The provider's random number can be retrieved + // from them in a provider-dependent manner. + // + // For the moment, the provider 0x368397bDc956b4F23847bE244f350Bde4615F25E hosts a webservice at + // https://fortuna-staging.pyth.network/ that allows anyone to retrieve their random values. + // Fetch the following url: + // https://fortuna-staging.pyth.network/v1/chains//revelations/ + // + // The list of supported chain ids is available here https://fortuna-staging.pyth.network/v1/chains + // + // **Warning** users of this protocol can stall the protocol by choosing not to reveal their generated random number. + // Developers using Pyth Entropy should ensure that users are always incentivized (or at least, not disincentivized) + // to finish both stages of the protocol. + function revealFlip( + uint64 sequenceNumber, + bytes32 userRandom, + bytes32 providerRandom + ) public { + // Validate that the caller is allowed to reveal the result of this particular in-flight request. + if (requestedFlips[sequenceNumber] != msg.sender) { + revert CoinFlipErrors.IncorrectSender(); + } + // Optional: delete the in-flight request to save gas / chain storage. + delete requestedFlips[sequenceNumber]; + + // Reveal the random number. This call reverts if the provided values fail to match the commitments + // from the request phase. If the call returns, randomNumber is a uniformly distributed bytes32. + bytes32 randomNumber = entropy.reveal( + entropyProvider, + sequenceNumber, + userRandom, + providerRandom + ); + + // You can then convert the returned bytes32 into the range required by your application. + emit FlipResult(uint256(randomNumber) % 2 == 0); + } + + receive() external payable {} +} diff --git a/target_chains/ethereum/sdk/solidity/package.json b/target_chains/ethereum/sdk/solidity/package.json index e822c5559b..ea66a8ba46 100644 --- a/target_chains/ethereum/sdk/solidity/package.json +++ b/target_chains/ethereum/sdk/solidity/package.json @@ -17,7 +17,7 @@ "solidity", "oracle" ], - "author": "Pyth Data Foundation", + "author": "Pyth Data Association", "license": "Apache-2.0", "bugs": { "url": "https://github.com/pyth-network/pyth-crosschain/issues"