diff --git a/docs/dev/tutorials/ftso/getting-random-numbers.md b/docs/dev/tutorials/ftso/getting-random-numbers.md new file mode 100644 index 000000000..26cc83e97 --- /dev/null +++ b/docs/dev/tutorials/ftso/getting-random-numbers.md @@ -0,0 +1,111 @@ +# Getting Random Numbers + +This tutorial shows how to obtain random numbers from the [Flare Systems Protocol (FSP)](../../../tech/flare-systems-protocol.md), the infrastructure that powers most current Flare protocols. +The source of the randomness is the submissions from all [FTSO data providers](../../../tech/ftso.md#procedure-overview) and is therefore not centralized. + +Random numbers are generated every 90 seconds and can be read directly from a smart contract. + +This is useful in several development contexts where secure, fair random numbers are required, such as in games and certain blockchain protocol functionalities such as [selecting a random vote power block](../../../tech/ftso.md#vote-power). + +!!! info "Security and Fairness" + + A generated random number is tagged as secure if all data providers correctly followed the FTSO protocol and at least one of them is not malicious. + + If a number is tagged as secure, then the protocol guarantees its fairness, meaning that it has no bias and all outcomes are equally probable. + +This tutorial shows: + +* How to obtain a random number. +* How to use the Flare periphery packages to simplify working with the Flare API. + +## Code + +Choose your preferred programming language and ensure you have a working [development environment](../../getting-started/setup/index.md). + +For easy navigation, numbered comments in the source code (e.g. `// 1.`) link to the tutorial sections below. + +{% import "runner.md" as runner with context %} + +=== "Solidity" + + {{ runner.sol("ftso/", "GetRandomNumber") | indent(4) }} + +=== "JavaScript" + + {{ runner.js("ftso/", "GetRandomNumber", runFromBrowser='false') | indent(4) }} + + + +
+ +## Tutorial + +### 1. Import Dependencies + +The tutorial uses the following dependencies: + +* The [Flare Periphery Package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) for Solidity and the [Flare Periphery Artifacts Package](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contract-artifacts) for JavaScript, which provide the API for all Flare smart contracts. + +* If you use JavaScript, the [ethers](https://www.npmjs.com/package/ethers) package is also needed to work with smart contracts. + +{{ runner.multisnippet("ftso/GetRandomNumber", 3, 5, 8, 11) }} + +The Periphery Packages simplify working with the Flare smart contracts significantly. + +!!! warning + If you remove this dependency, you must manually provide the signatures for all the methods you want to use. + +### 2. Access the Contract Registry + +The [`FlareContractRegistry`](FlareContractRegistry.md) contains the current addresses for all Flare smart contracts, and it is [the only recommended way](../../getting-started/contract-addresses.md) to retrieve them. + +=== "Solidity" + + The `FlareContractsRegistryLibrary` contract from the Flare Periphery Package accesses the Flare Contract Registry for you, as shown next. + +=== "JavaScript" + + The address of the Flare Contract Registry is the same on all of [Flare's networks](../../../tech/flare.md#flare-networks), and it is the only Flare address that needs to be hard-coded into any program. + + ```javascript title="GetRandomNumber.js" linenums="3" + --8<-- "./docs/samples/ftso/GetRandomNumber.js:3:4" + ``` + + ```javascript title="GetRandomNumber.js" linenums="13" + --8<-- "./docs/samples/ftso/GetRandomNumber.js:13:17" + ``` + +### 3. Retrieve the Relay Contract + +Use the [`getContractAddressByName()`](FlareContractRegistry.md#fn_getcontractaddressbyname_82760fca) method of the [`FlareContractRegistry`](FlareContractRegistry.md) smart contract to retrieve the address of the `Relay` smart contract. + +{{ runner.multisnippet("ftso/GetRandomNumber", 9, 11, 20, 27) }} + +### 4. Get the Random Number + +Get the latest generated random number by calling the `getRandomNumber()` method of the `Relay` contract. + +{{ runner.multisnippet("ftso/GetRandomNumber", 12, 15, 30, 33) }} + +In addition to the `randomNumber`, two other variables are retrieved: + +* `isSecure` is a boolean flag that indicates whether the random number was generated securely, according to the description given in the introduction. + + The random number is based on all the data providers' submissions and is therefore decentralized, improving transparency and fairness. + However, this decentralization makes the protocol slightly open to attempts at manipulation. + If such manipulation attempts are detected, the `isSecure` flag is set to `false`, and dapps can decide whether they should discard the generated number. + +* `timestamp` is the [UNIX timestamp](https://en.wikipedia.org/wiki/Unix_time) indicating the time at the end of the voting epoch during which data was collected from data providers to generate this particular number. + + The timestamp can be useful, for example, to ensure that certain actions have been performed before the random number was generated. + For example, in a roulette game, to ensure all bets were placed before the number was generated. + Each voting epoch is a fixed 90-second window. + +## Conclusion + +This tutorial has shown: + +* How to use the Flare Periphery Package, both from [Solidity](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contracts) and from [JavaScript](https://www.npmjs.com/package/@flarenetwork/flare-periphery-contract-artifacts), to work with the Flare API. +* How to get the latest random number via the `Relay` contract. diff --git a/docs/dev/tutorials/ftso/index.md b/docs/dev/tutorials/ftso/index.md index 79af91f28..fb9fc983b 100644 --- a/docs/dev/tutorials/ftso/index.md +++ b/docs/dev/tutorials/ftso/index.md @@ -6,3 +6,4 @@ These code samples and explanations show how to use the [FTSO system](../../../t * [FTSO Developer Overview](../../../dev/reference/ftso.md) * [Getting FTSO Data Feeds](../ftso/getting-data-feeds.md) +* [Getting Random Numbers](../ftso/getting-random-numbers.md) diff --git a/docs/samples/ftso/GetRandomNumber.js b/docs/samples/ftso/GetRandomNumber.js new file mode 100644 index 000000000..d4e495251 --- /dev/null +++ b/docs/samples/ftso/GetRandomNumber.js @@ -0,0 +1,36 @@ +const FLARE_CONTRACTS = "@flarenetwork/flare-periphery-contract-artifacts"; +const FLARE_RPC = "https://coston-api.flare.network/ext/C/rpc"; +const FLARE_CONTRACT_REGISTRY_ADDR = + "0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019"; + +async function runGetRandomNumber() { + // 1. Import Dependencies + const ethers = await import("ethers"); + const flare = await import(FLARE_CONTRACTS); + const provider = new ethers.JsonRpcProvider(FLARE_RPC); + + // 2. Access the Contract Registry + const flareContractRegistry = new ethers.Contract( + FLARE_CONTRACT_REGISTRY_ADDR, + flare.nameToAbi("FlareContractRegistry", "coston").data, + provider + ); + + // 3. Retrieve the Relay Contract + const relayAddress = await flareContractRegistry.getContractAddressByName( + "Relay" + ); + const relay = new ethers.Contract( + relayAddress, + flare.nameToAbi("IRelay", "coston").data, + provider + ); + + // 4. Get the Random Number + const [randomNumber, isSecure, timestamp] = await relay.getRandomNumber(); + console.log("Random Number is", randomNumber); + console.log("Is it secure", isSecure); + console.log("Creation timestamp is", timestamp); +} + +runGetRandomNumber(); \ No newline at end of file diff --git a/docs/samples/ftso/GetRandomNumber.sol b/docs/samples/ftso/GetRandomNumber.sol new file mode 100644 index 000000000..e65b1bef3 --- /dev/null +++ b/docs/samples/ftso/GetRandomNumber.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.6 <0.9; + +import {IRelay} from "@flarenetwork/flare-periphery-contracts/coston/util-contracts/userInterfaces/IRelay.sol"; +import {FlareContractsRegistryLibrary} from "@flarenetwork/flare-periphery-contracts/coston/util-contracts/ContractRegistryLibrary.sol"; + +contract GetRandomNumber { + function generateNumber() external view returns (uint256, bool, uint256) { + address relayAddress = + FlareContractsRegistryLibrary.getContractAddressByName("Relay"); + IRelay relay = IRelay(relayAddress); + (uint256 randomNumber, bool isSecure, uint256 timestamp) = + relay.getRandomNumber(); + + return (randomNumber, isSecure, timestamp); + } +} \ No newline at end of file diff --git a/docs/samples/ftso/GetRandomNumber.t.sol b/docs/samples/ftso/GetRandomNumber.t.sol new file mode 100644 index 000000000..c39e6dc67 --- /dev/null +++ b/docs/samples/ftso/GetRandomNumber.t.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// Import dependencies +import "forge-std/Test.sol"; +import "../src/GetRandomNumber.sol"; + +// Test Contract +contract GetRandomNumberTest is Test { + string private constant FLARE_RPC = + "https://flare-api.flare.network/ext/bc/C/rpc"; + uint256 private flareFork; + + function setUp() public { + flareFork = vm.createFork(FLARE_RPC); + } + + function testRandomNumber() public { + vm.selectFork(flareFork); + GetRandomNumber randNumber = new GetRandomNumber(); + + (uint256 _randomNumber, bool _isSecure, uint256 _timeStamp) = randNumber + .getRandomNumber(); + + assertGt(_randomNumber, 0, "Random Number expected to be > 0"); + assertTrue(_isSecure, "Expect to be true"); + assertGt( + _timestamp, + 1695817332, + "Timestamp expected to be greater than a known past block" + ); + } +} \ No newline at end of file diff --git a/docs/samples/ftso/TestGetRandomNumber.js b/docs/samples/ftso/TestGetRandomNumber.js new file mode 100644 index 000000000..e7e6e4b8c --- /dev/null +++ b/docs/samples/ftso/TestGetRandomNumber.js @@ -0,0 +1,16 @@ +const { expect } = require("chai"); +describe("Test Random Number", function () { + let contract; + beforeEach(async function () { + contract = await ethers.deployContract("GetRandomNumber"); + }); + + it("RandomNumber", async function () { + const [randomNumber, isSecure, timestamp] = await contract.generateNumber(); + expect(randomNumber).to.be.at.least( + 1000000000000000000000000000000000000000n + ); + expect(isSecure).to.be.true; + expect(timestamp).to.be.gt(1695817332); + }); +}); \ No newline at end of file diff --git a/include/runner.md b/include/runner.md index d136e8f49..dab89462c 100644 --- a/include/runner.md +++ b/include/runner.md @@ -20,7 +20,7 @@ 3. Initialize project and install dependencies with: ```bash npm init - npm install ethers@6.3 @flarenetwork/flare-periphery-contract-artifacts@0.1.7 + npm install ethers@6.3 @flarenetwork/flare-periphery-contract-artifacts@0.1.15 ``` 4. Run the program with: ```bash diff --git a/mkdocs.yml b/mkdocs.yml index 1f4b97c56..0f614c936 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -232,6 +232,7 @@ nav: - dev/tutorials/ftso/index.md - dev/reference/ftso.md - dev/tutorials/ftso/getting-data-feeds.md + - dev/tutorials/ftso/getting-random-numbers.md - State Connector: - dev/tutorials/sc/index.md - dev/tutorials/sc/address-validity.md