Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add base & implementation solidity contracts to demo secure randomness #20

Merged
merged 21 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5f14a52
add initial solidity contracts
sisyphusSmiling Sep 13, 2024
cc64704
forge install: forge-std
sisyphusSmiling Sep 13, 2024
554c778
fix CoinToss.flipCoin() on 0 value transmission
sisyphusSmiling Sep 13, 2024
72e386b
fix CoinToss.sol & add test coverage
sisyphusSmiling Sep 17, 2024
0b7cc24
Merge branch 'main' into add-solidity-impl
sisyphusSmiling Sep 17, 2024
01c5903
update dependencies
sisyphusSmiling Sep 17, 2024
a166fe6
add Cadence transactions to interact with CoinToss.sol
sisyphusSmiling Sep 17, 2024
f535eea
add foundry tests to ci
sisyphusSmiling Sep 17, 2024
8f5797b
rename transactions
sisyphusSmiling Sep 17, 2024
13a7274
add CadenceArchWrapper contract & implement in CoinToss.sol
sisyphusSmiling Sep 17, 2024
8e05274
reformat CoinToss.sol
sisyphusSmiling Sep 17, 2024
5459027
update README
sisyphusSmiling Sep 17, 2024
c909e77
Update README.md
sisyphusSmiling Sep 19, 2024
5861871
update statements relating to PRG implementation details
sisyphusSmiling Sep 19, 2024
cf1ec53
fix CadenceRandomConsumer._requestRandomness as internal
sisyphusSmiling Sep 20, 2024
e779414
add missing receive() function to CoinToss
sisyphusSmiling Sep 20, 2024
802c871
update .gitignore
sisyphusSmiling Sep 20, 2024
86c24ec
fix getRandomSource precompile call
sisyphusSmiling Sep 20, 2024
839d697
update CoinToss.sol deployment
sisyphusSmiling Sep 20, 2024
9be7f77
add Cadence tests covering CoinToss.sol interactions via COA
sisyphusSmiling Sep 20, 2024
3b4176e
add Cadence tests CI workflow action
sisyphusSmiling Sep 20, 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
30 changes: 30 additions & 0 deletions .github/workflows/cadence_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: CI

on: pull_request

jobs:
tests:
name: Flow CLI Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: "1.20.x"
- uses: actions/cache@v1
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Install Flow CLI
run: sh -ci "$(curl -fsSL https://raw.githubusercontent.com/onflow/flow-cli/master/install.sh)"
- name: Flow CLI Version
run: flow version
- name: Update PATH
run: echo "/root/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: flow deps install
- name: Run tests
run: flow test --cover --covercode="contracts" --coverprofile="coverage.lcov" ./tests/*_tests.cdc
34 changes: 34 additions & 0 deletions .github/workflows/foundry_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: test

on: pull_request

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Run Forge build
run: |
forge --version
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
File renamed without changes.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
# Local keys
*.pem
*.pkey

# Cadence dependencies
imports/

# Cadence testing
coverage.lcov
coverage.json

# Foundry directories
cache/
out/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "solidity/lib/forge-std"]
path = solidity/lib/forge-std
url = https://github.com/foundry-rs/forge-std
52 changes: 28 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
# Random Coin Toss

While both are backed by Flow's Random Beacon,
> :information_source: This repository contains demonstrations for safe usage of Flow's protocol-native secure randomness in both Cadence and Solidity smart contracts.

On Flow, there are two routes to get a random value. While both are backed by Flow's Random Beacon,
it is important for developers to mindfully choose between `revertibleRandom`
or seeding their own PRNG utilizing the `RandomBeaconHistory` smart contract:

- Under the hood, the FVM also just instantiates a PRNG for each transaction that `revertibleRandom` draws from.
Though, with `revertibleRandom` a developer is calling the PRNG that is controlled by the transaction,
which also has the power to abort and revert if it doesn't like `revertibleRandom`'s outputs.
`revertibleRandom` is only suitable for smart contract functions that exclusively run within the trusted transactions.
- In contrast, using the `RandomBeaconHistory` means to use a deterministically-seeded PRNG.
The `RandomBeaconHistory` is key for effectively implementing a commit-and-reveal scheme.
During the commit phase, the user commits to proceed with a future source of randomness,
which is revealed after the commit transaction concluded.
For each block, the `RandomBeaconHistory` automatically stores the subsequently generated source of randomness.

> 🚨 A transaction can atomically revert all its action during its runtime and abort.
- When using `revertibleRandom` a developer is relying on randomness generation controlled by the transaction,
which also has the power to abort and revert based on `revertibleRandom`'s outputs. Therefore,
`revertibleRandom` is only suitable for smart contract functions that exclusively run within credibly-neutral transactions which a developer can trust won't revert based on undesirable random outputs.
- In contrast, using the `RandomBeaconHistory` allows developers to use a committed random source (or seed) that can't be reverted.
The `RandomBeaconHistory` is key for effectively implementing a commit-and-reveal scheme which is itself a pattern that prevents users from gaming a transaction based on the result of randomness.
During the commit phase, the user commits to proceed with a **future** source of randomness
which is revealed after the commit transaction concludes.
For each block, the `RandomBeaconHistory` automatically stores the subsequently generated source of randomness in the final transaction of that block. This source of randomness is committed by Flow's protocol service account.

> 🚨 A transaction can atomically revert the entirety of its action based on results exposed within the scope of that transaction.
> Therefore, it is possible for a transaction calling into your smart contract to post-select favorable
> results and revert the transaction for unfavorable results.
>
Expand All @@ -24,8 +25,7 @@ or seeding their own PRNG utilizing the `RandomBeaconHistory` smart contract:
>
> ✅ Utilizing a commit-and-reveal scheme is important for developers to protect their smart contracts from transaction post-selection attacks.


Via a commit-and-reveal scheme, flow's native secure randomness can be safely used within Cadence smart contracts
Via a commit-and-reveal scheme, Flow's protocol-native secure randomness can be safely used within both Cadence and Solidity smart contracts
when contracts are transacted on by untrusted parties.
By providing examples of commit-reveal implementations we hope to foster a more secure ecosystem of decentralized
applications and encourage developers to build with best practices.
Expand All @@ -36,11 +36,11 @@ The contracts contained in this repo demonstrate how to use Flow's onchain rando
in contracts that are transacted on by untrusted parties. Safe randomness here meaning non-revertible randomness,
i.e. mitigating post-selection attacks via a commit-and-reveal scheme.

Random sources are committed to the [`RandomBeaconHistory` contract](./contracts/RandomBeaconHistory.cdc) by the service
Random sources are committed to the [`RandomBeaconHistory` contract](https://github.com/onflow/flow-core-contracts/blob/master/contracts/RandomBeaconHistory.cdc) by the service
account at the end of every block. The RandomBeaconHistory contract provides a convenient archive, where for each past
block height (starting Nov 2023) the respective 'source of randomness' can be retrieved.

When used naively, `revertibleRandom` as well as the [`RandomBeaconHistory` contract](./contracts/RandomBeaconHistory.cdc)
When used naively, `revertibleRandom` as well as the [`RandomBeaconHistory` contract](https://github.com/onflow/flow-core-contracts/blob/master/contracts/RandomBeaconHistory.cdc)
are subject to post-selection attacks from transactions.
In simple terms, using the random source in your contract without
the protection of a commit-reveal mechanism would enable non-trusted callers to condition their interactions with your contract on the
Expand All @@ -49,11 +49,11 @@ game.

To achieve non-revertible randomness, the contract should be structured to resolve in two phases:

1. Commit - Caller commits to the resolution of their bet with some yet unknown source of randomness (i.e. in the
future)
2. Reveal - Caller can then resolve the result of their bet once the source of randomness is available in the `RandomBeaconHistory` with a separate transaction.
From a technical perspective, this could also be called "resolving transaction", because the transaction simply executes the smart contract with the locked-in
inputs, whose output all parties committed to accept in the previous phase.
1. **Commit** - Caller commits to the resolution of their bet with some yet unknown source of randomness (i.e. in the
future)
2. **Reveal** - Caller can then resolve the result of their bet once the source of randomness is available in the `RandomBeaconHistory` with a separate transaction.
From a technical perspective, this could also be called a "resolving transaction", because the transaction simply executes the smart contract with the locked-in
inputs, whose output all parties committed to accept in the previous phase.

Though a caller could still condition the revealing transaction on the coin flip result, all the inputs influencing the bet's outcome
have already been fixed (the source of randomness being the last one that is only generated after the commit transaction concluded).
Expand All @@ -63,14 +63,17 @@ All that the resolving transaction (reveal phase) is doing is affirming the win
The ticket owner could revert their resolving transaction. Though that does not change whether the ticket won or lost. Furthermore, the player has already
incurred the cost of their bet and gains nothing by reverting the reveal step.

Given that Flow has both Cadence and EVM runtimes, commit-reveal patterns covering Cadence and Solidity are found in this repo as well as transactions demonstrating how Flow accounts can interact with EVM implementations from the Cadence runtime via COAs.

## Deployments

|Contract|Testnet|Mainnet|
|---|---|---|
|[CoinToss](./contracts/CoinToss.cdc)|[0xd1299e755e8be5e7](https://contractbrowser.com/A.d1299e755e8be5e7.CoinToss)|N/A|
|[Xorshift128plus](./contracts/Xorshift128plus.cdc)|[0xed24dbe901028c5c](https://contractbrowser.com/A.ed24dbe901028c5c.Xorshift128plus)|[0x45caec600164c9e6](https://contractbrowser.com/A.45caec600164c9e6.Xorshift128plus)|
|[CoinToss.cdc](./contracts/CoinToss.cdc)|[0xd1299e755e8be5e7](https://contractbrowser.com/A.d1299e755e8be5e7.CoinToss)|N/A|
|[Xorshift128plus.cdc](./contracts/Xorshift128plus.cdc)|[0xed24dbe901028c5c](https://contractbrowser.com/A.ed24dbe901028c5c.Xorshift128plus)|[0x45caec600164c9e6](https://contractbrowser.com/A.45caec600164c9e6.Xorshift128plus)|
|[CoinToss.sol](./contracts/CoinToss.sol)|[0x5FC8d32690cc91D4c39d9d3abcBD16989F875707](https://evm-testnet.flowscan.io/address/0x5FC8d32690cc91D4c39d9d3abcBD16989F875707?tab=contract_code)|N/A|

## Further Reading
## Further Reading


- We recommend the **Flow developer documentation** [**_Advanced Concepts → Flow VRF_**](https://developers.flow.com/build/advanced-concepts/randomness)
Expand All @@ -80,3 +83,4 @@ incurred the cost of their bet and gains nothing by reverting the reveal step.
- [Pull request introducing the `RandomBeaconHistory` system smart contract.](https://github.com/onflow/flow-core-contracts/pull/375)
- [FLIP 123: _On-Chain randomness history for commit-reveal schemes_](https://github.com/onflow/flips/pull/123) describes the need for a commit-and-reveal scheme and
discusses ideas for additional convenience functionality to further optimize the developer experience in the future.
- For more on Cadence Arch pre-compiles and accessing random values from EVM on Flow, see documentation on the [Cadence Arch precompiled contracts](https://developers.flow.com/evm/how-it-works#precompiled-contracts).
42 changes: 26 additions & 16 deletions flow.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,18 @@
},
"dependencies": {
"Burner": {
"source": "previewnet://b6763b4399a888c8.Burner",
"hash": "9f84f00c8d2afd70916514e56f9459ef7f426b70a7440dd1b64f2723ff779ce7",
"source": "mainnet://f233dcee88fe0abe.Burner",
"hash": "71af18e227984cd434a3ad00bb2f3618b76482842bae920ee55662c37c8bf331",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "e467b9dd11fa00df",
"previewnet": "b6763b4399a888c8",
"testnet": "8c5303eaa26202d6"
}
},
"EVM": {
"source": "mainnet://e467b9dd11fa00df.EVM",
"hash": "1b1f3fe59d964b8afde33d3150ab89f257373aa253ae412c8b02fb176dd03698",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "e467b9dd11fa00df",
Expand All @@ -29,8 +39,8 @@
}
},
"FlowToken": {
"source": "previewnet://4445e7ad11568276.FlowToken",
"hash": "633b6a3cd0a3d68cb665478b297f9f3e37cc9c6c8829a4fda5b22ed7cb908a44",
"source": "mainnet://1654653399040a61.FlowToken",
"hash": "cefb25fd19d9fc80ce02896267eb6157a6b0df7b1935caa8641421fe34c0e67a",
"aliases": {
"emulator": "0ae53cb6e3f42a79",
"mainnet": "1654653399040a61",
Expand All @@ -39,8 +49,8 @@
}
},
"FungibleToken": {
"source": "previewnet://a0225e7000ac82a9.FungibleToken",
"hash": "3cad0cb7d88b8af34ab82a1580c6d70906649f7e974c12958a55c822d4f1e808",
"source": "mainnet://f233dcee88fe0abe.FungibleToken",
"hash": "1410889b47fef8b02f6867eef3d67a75288a56a651b67a7e815ce273ad301cff",
"aliases": {
"emulator": "ee82856bf20e2aa6",
"mainnet": "f233dcee88fe0abe",
Expand All @@ -49,8 +59,8 @@
}
},
"FungibleTokenMetadataViews": {
"source": "previewnet://a0225e7000ac82a9.FungibleTokenMetadataViews",
"hash": "2206d6ac3d18e2a26ed8e610cb28d1e054423b20a8bdc2ac6a10bffc7e579a9c",
"source": "mainnet://f233dcee88fe0abe.FungibleTokenMetadataViews",
"hash": "294ada6a3df68757fcac4d794f62307c2ea4fe49c93f67e3771d3c6d8377dd47",
"aliases": {
"emulator": "ee82856bf20e2aa6",
"mainnet": "f233dcee88fe0abe",
Expand All @@ -59,8 +69,8 @@
}
},
"MetadataViews": {
"source": "previewnet://b6763b4399a888c8.MetadataViews",
"hash": "a49bab604f143ced27662b28d16887707ed7a4e60e626eb1e742ea93bf0bffb0",
"source": "mainnet://1d7e57aa55817448.MetadataViews",
"hash": "be26ea7959d7cbc06ac69fe00926b812c4da67984ea2d1bde1029141ae091378",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "1d7e57aa55817448",
Expand All @@ -69,8 +79,8 @@
}
},
"NonFungibleToken": {
"source": "previewnet://b6763b4399a888c8.NonFungibleToken",
"hash": "ebf4d24a324803365c238d95c621ee472f36acc1ed4158ff5d255e8fb865e3cc",
"source": "mainnet://1d7e57aa55817448.NonFungibleToken",
"hash": "49a58b950afdaf0728fdb7d4eb47cf4f2ec3077d655f274b7fdeb504c742f528",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "1d7e57aa55817448",
Expand All @@ -79,8 +89,8 @@
}
},
"RandomBeaconHistory": {
"source": "previewnet://b6763b4399a888c8.RandomBeaconHistory",
"hash": "9536f036aed0afbd5c84b4ce4a1def7466bff989bcb07c7be9918028b46f05db",
"source": "mainnet://e467b9dd11fa00df.RandomBeaconHistory",
"hash": "fe7f11c46dd54446bd60d88015ffe5057d7245c4b77374e11db2adf0a8156167",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "e467b9dd11fa00df",
Expand All @@ -89,8 +99,8 @@
}
},
"ViewResolver": {
"source": "previewnet://b6763b4399a888c8.ViewResolver",
"hash": "eaa3214b0e178c5692cfc243a14c739150d5d57ef3f9204a252d70667a67db76",
"source": "mainnet://1d7e57aa55817448.ViewResolver",
"hash": "374a1994046bac9f6228b4843cb32393ef40554df9bd9907a702d098a2987bde",
"aliases": {
"emulator": "f8d6e0586b0a20c7",
"mainnet": "1d7e57aa55817448",
Expand Down
8 changes: 8 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[profile.default]
src = "./solidity/src"
out = "./solidity/out"
libs = ["./solidity/lib"]
script = "./solidity/script"
test = "./solidity/test"

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions solidity/lib/forge-std
Submodule forge-std added at 1714be
61 changes: 61 additions & 0 deletions solidity/src/CadenceArchWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

/**
* @dev This contract is a base contract to facilitate easier consumption of the Cadence Arch pre-compiles. Implementing
* contracts can use this contract to fetch the current Flow block height and fetch random numbers from the Cadence
* runtime.
*/
abstract contract CadenceArchWrapper {
// Cadence Arch pre-compile address
address public constant cadenceArch = 0x0000000000000000000000010000000000000001;

/**
* @dev This method returns the current Flow block height.
*
* @return flowBlockHeight The current Flow block height.
*/
function _flowBlockHeight() internal view returns (uint64) {
(bool ok, bytes memory data) = cadenceArch.staticcall(abi.encodeWithSignature("flowBlockHeight()"));
require(ok, "Unsuccessful call to Cadence Arch pre-compile when fetching Flow block height");

uint64 output = abi.decode(data, (uint64));
return output;
}

/**
* @dev This method uses the Cadence Arch pre-compiles to return a random number from the Cadence runtime. Consumers
* should know this is a revertible random source and should only be used as a source of randomness when called by
* trusted callers - i.e. with trust that the caller won't revert on result.
*
* @return randomSource The random source.
*/
function _revertibleRandom() internal view returns (uint64) {
(bool ok, bytes memory data) = cadenceArch.staticcall(abi.encodeWithSignature("revertibleRandom()"));
require(ok, "Unsuccessful call to Cadence Arch pre-compile when fetching revertible random number");

uint64 output = abi.decode(data, (uint64));
return output;
}

/**
* @dev This method uses the Cadence Arch pre-compiles to returns a random source for a given Flow block height.
* The provided height must be at least one block in the past.
*
* @param flowHeight The Flow block height for which to get the random source.
* @return randomSource The random source for the given Flow block height.
*/
function _getRandomSource(uint64 flowHeight) internal view returns (uint64) {
(bool ok, bytes memory data) =
cadenceArch.staticcall(abi.encodeWithSignature("getRandomSource(uint64)", flowHeight));
require(ok, "Unsuccessful call to Cadence Arch pre-compile when fetching random source");

// Decode the result as bytes32 and then cast it to uint64
bytes32 result = abi.decode(data, (bytes32));

// Convert the bytes32 result to uint64 by casting, taking the least significant 8 bytes
uint64 output = uint64(uint256(result));

return output;
}
}
Loading
Loading