diff --git a/apps/randomness/Makefile b/apps/randomness/Makefile index 1434629bcd..d5913ff6df 100644 --- a/apps/randomness/Makefile +++ b/apps/randomness/Makefile @@ -5,14 +5,21 @@ include ../../makefiles/formatting.mk include ../../makefiles/bundling.mk include ../../makefiles/help.mk +setup-local: ## Sets up local environment and runs everything + ./start-local-randomness.sh +.PHONY: setup-local + +stop-local: ## Kills anvil and the randomness service + ./stop-local-randomness.sh +.PHONY: stop-local + start: ## Starts the randomness service node --env-file=.env dist/index.es.js -PHONY: start - +.PHONY: start migrate: ## Runs pending migrations tsx --env-file=.env src/migrate.ts -PHONY: migrate +.PHONY: migrate link-anvil: rm src/ABI/random.ts @@ -22,4 +29,4 @@ link-anvil: link-happy-sepolia: rm src/ABI/random.ts ln -s ../../../../contracts/deployments/happy-sepolia/random/abis.ts src/ABI/random.ts -.PHONY: link-happy-sepolia \ No newline at end of file +.PHONY: link-happy-sepolia diff --git a/apps/randomness/src/CustomGasEstimator.ts b/apps/randomness/src/CustomGasEstimator.ts index dba66bb066..f88a4fb24b 100644 --- a/apps/randomness/src/CustomGasEstimator.ts +++ b/apps/randomness/src/CustomGasEstimator.ts @@ -11,8 +11,11 @@ export class CustomGasEstimator extends DefaultGasLimitEstimator { transactionManager: TransactionManager, transaction: Transaction, ): Promise> { + // These values are based on benchmarks from Anvil. + // An extra margin is added to prevent errors in the randomness service due to minor contract changes. + if (transaction.functionName === "postCommitment") { - return ok(50000n) + return ok(75000n) } if (transaction.functionName === "revealValue") { return ok(100000n) diff --git a/apps/randomness/start-local-randomness.sh b/apps/randomness/start-local-randomness.sh new file mode 100755 index 0000000000..21fcb549af --- /dev/null +++ b/apps/randomness/start-local-randomness.sh @@ -0,0 +1,246 @@ +#!/bin/bash + +# ----------------------------------------------------------------------------- +# This script automates the setup and deployment of the Randomness Service on a local host environment. +# It performs the following tasks: +# 1. Verifies that all required tools are installed. +# 2. Cleans up any existing Anvil process and randomness service processes. +# 3. Starts the Anvil local blockchain with specified configurations. +# 4. Retrieves the genesis block timestamp and updates the HAPPY_GENESIS_ENV_VAR environment variable. +# 5. Deploys necessary smart contracts and fetches the latest Drand round to set the DRAND_ROUND_ENV_VAR environment variable. +# 6. Prepares and migrates SQLite databases for transaction management and randomness. +# 7. Builds and starts the Randomness Service +# ----------------------------------------------------------------------------- + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + + +ANVIL_RPC_PORT=8545 +ANVIL_RPC_URL="http://127.0.0.1:$ANVIL_RPC_PORT" +BLOCK_TIME=2 +HAPPY_GENESIS_ENV_VAR="HAPPY_GENESIS_TIMESTAMP_SECONDS" +DRAND_ROUND_ENV_VAR="EVM_DRAND_START_ROUND" +DRAND_URL="https://api.drand.sh/v2/beacons/evmnet/rounds/latest" +RANDOMNESS_DB_PATH="$SCRIPT_DIR/randomness.sqlite" +TXM_DB_PATH="$SCRIPT_DIR/transaction-manager.sqlite" +PM2_PROCESS_NAME="randomness-service" +REQUIRED_TOOLS=("anvil" "curl" "jq" "sqlite3" "sed" "make" "nc" "cast" "pgrep" "pkill" "grep" "awk") + + +# Function to add or update a variable in a .env file +set_env_var() { + local FILE_PATH="$1" + local VAR_NAME="$2" + local VAR_VALUE="$3" + local HAS_EXPORT="$4" + + # Check if all three arguments are provided + if [[ -z "$FILE_PATH" || -z "$VAR_NAME" || -z "$VAR_VALUE" ]]; then + echo "Usage: set_env_var " + return 1 + fi + + local PREFIX="" + if [[ "$HAS_EXPORT" == "true" ]]; then + PREFIX="export " + fi + + if grep -q "^${PREFIX}${VAR_NAME}=" "$FILE_PATH"; then + # Variable exists; replace its value + if sed --version >/dev/null 2>&1; then + # GNU sed (common on Linux) + sed -i "s/^${PREFIX}${VAR_NAME}=.*/${PREFIX}${VAR_NAME}=${VAR_VALUE}/" "$FILE_PATH" + else + # BSD sed (common on macOS) + sed -i '' "s/^${PREFIX}${VAR_NAME}=.*/${PREFIX}${VAR_NAME}=${VAR_VALUE}/" "$FILE_PATH" + fi + + if [[ $? -eq 0 ]]; then + echo "The variable '${VAR_NAME}' has been updated in '${FILE_PATH}'." + else + echo "Error: Could not update the variable '${VAR_NAME}' in '${FILE_PATH}'." + return 1 + fi + else + # Variable does not exist; append it to the file + echo "\n${PREFIX}${VAR_NAME}=${VAR_VALUE}" >> "$FILE_PATH" + if [[ $? -eq 0 ]]; then + echo "The variable '${VAR_NAME}' has been added to '${FILE_PATH}'." + else + echo "Error: Could not add the variable '${VAR_NAME}' to '${FILE_PATH}'." + return 1 + fi + fi + + return 0 +} + + +empty_sqlite_db() { + local DB_PATH="$1" + + if [[ -f "$DB_PATH" ]]; then + tables=$(sqlite3 "$DB_PATH" ".tables") + + if [[ -z "$tables" ]]; then + echo "The database '$DB_PATH' is empty or has no tables." + return 0 + fi + + for table in $tables; do + echo $table + sqlite3 "$DB_PATH" "DROP TABLE \"$table\";" + if [[ $? -eq 0 ]]; then + echo "Data deleted from table '$table' in '$DB_PATH'." + else + echo "Error deleting data from table '$table' in '$DB_PATH'." + return 1 + fi + done + + echo "The database '$DB_PATH' has been successfully emptied." + else + echo "The database '$DB_PATH' does not exist. A new empty database will be created." + sqlite3 "$DB_PATH" "" + if [[ $? -eq 0 ]]; then + echo "Database '$DB_PATH' created." + else + echo "Error creating the database '$DB_PATH'." + return 1 + fi + fi +} + +check_tools() { + local missing_tools=() + + for tool in "${REQUIRED_TOOLS[@]}"; do + if ! command -v "$tool" > /dev/null 2>&1; then + missing_tools+=("$tool") + fi + done + + if [ ${#missing_tools[@]} -ne 0 ]; then + echo "Error: The following tools are not installed:" + for tool in "${missing_tools[@]}"; do + echo " - $tool" + done + echo "Please install the missing tools and re-run the script." + exit 1 + fi +} + + + +check_tools + +mkdir -p "$SCRIPT_DIR/logs" + +if pgrep anvil > /dev/null; then + pkill anvil +fi + +if [[ -f "$SCRIPT_DIR/logs/randomness_service.pid" ]]; then + RANDOMNESS_PID=$(cat logs/randomness_service.pid) + if [[ -n "$RANDOMNESS_PID" ]] && kill -0 "$RANDOMNESS_PID" 2>/dev/null; then + kill "$RANDOMNESS_PID" + echo "Randomness service with PID $RANDOMNESS_PID has been stopped." + else + echo "No running process found for PID $RANDOMNESS_PID." + fi +else + echo "PID file does not exist. No process to stop." +fi + +rm -rf logs +mkdir logs + +if [[ -f ".env.bak" ]]; then + echo "Error: Backup file '.env.bak' already exists. Refusing to overwrite your existing backup." + exit 1 +else + cp .env .env.bak + echo "Backup created: '.env.bak'" +fi + +echo "Starting Anvil..." +anvil --block-time $BLOCK_TIME --port $ANVIL_RPC_PORT --chain-id 1337 > "$SCRIPT_DIR/logs/anvil.log" 2>&1 & +ANVIL_PID=$! + +# Limit the number of connection attempts to 5 +attempts=0 +max_attempts=5 + +while ! nc -z localhost $ANVIL_RPC_PORT; do + if [[ $attempts -ge $max_attempts ]]; then + echo "Failed to connect to Anvil after $max_attempts attempts. Exiting." + exit 1 + fi + sleep 1 + attempts=$((attempts + 1)) +done + echo "Anvil started with PID $ANVIL_PID." + +genesis_block=$(cast block 0 --rpc-url $ANVIL_RPC_URL) + +if [[ -z "$genesis_block" ]]; then + echo "Error: Failed to get genesis block" + exit 1 +fi + +genesis_timestamp=$(echo "$genesis_block" | grep '^timestamp' | awk '{print $2}') + +if [[ -z "$genesis_timestamp" ]]; then + echo "Error: Failed to get genesis timestamp" + exit 1 +fi + +echo "Genesis timestamp: $genesis_timestamp" + +echo "Setting environment variable $HAPPY_GENESIS_ENV_VAR to $genesis_timestamp" +set_env_var .env $HAPPY_GENESIS_ENV_VAR $genesis_timestamp false + +echo "Deploying contracts..." +make -C $SCRIPT_DIR/../../contracts deploy-random > $SCRIPT_DIR/logs/deploy-random.log 2>&1 +if [[ $? -ne 0 ]]; then + echo "Error: Failed to deploy contracts. Check the log file for details: $SCRIPT_DIR/logs/deploy-random.log" + exit 1 +fi + +echo "Contracts deployed" + +echo "Fetching Drand round..." + +round=$(curl -s $DRAND_URL | jq -r '.round') + +echo "Drand round: $round" + +echo "Setting environment variable $DRAND_ROUND_ENV_VAR to $round" +set_env_var .env $DRAND_ROUND_ENV_VAR $round false + +make -C $SCRIPT_DIR/../../packages/txm build + + +empty_sqlite_db $TXM_DB_PATH +echo $TXM_DB_PATH +export TXM_DB_PATH=$TXM_DB_PATH +make -C $SCRIPT_DIR/../../packages/txm migrate + +echo "Transaction manager migrated" + +make -C $SCRIPT_DIR build + +empty_sqlite_db $RANDOMNESS_DB_PATH +echo $RANDOMNESS_DB_PATH +export RANDOMNESS_DB_PATH=$RANDOMNESS_DB_PATH +make -C $SCRIPT_DIR migrate + +echo "Randomness service migrated" + +echo "Starting randomness service..." +make -C $SCRIPT_DIR start > $SCRIPT_DIR/logs/randomness-service.log 2>&1 & + +RANDOMNESS_SERVICE_PID=$! +echo "$RANDOMNESS_SERVICE_PID" > $SCRIPT_DIR/logs/randomness_service.pid + +echo "Randomness service started" \ No newline at end of file diff --git a/apps/randomness/stop-local-randomness.sh b/apps/randomness/stop-local-randomness.sh new file mode 100755 index 0000000000..319c490c80 --- /dev/null +++ b/apps/randomness/stop-local-randomness.sh @@ -0,0 +1,19 @@ + +#!/bin/bash + +# Minimal comments in English +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +if pgrep anvil > /dev/null; then + echo "Stopping anvil..." + pkill anvil +fi + +PID_FILE="$SCRIPT_DIR/logs/randomness_service.pid" +if [[ -f "$PID_FILE" ]]; then + RS_PID=$(cat "$PID_FILE") + if kill -0 "$RS_PID" 2>/dev/null; then + echo "Stopping randomness service..." + kill "$RS_PID" + fi +fi diff --git a/contracts/.env.example b/contracts/.env.example index d77fbed52e..96ca657bba 100755 --- a/contracts/.env.example +++ b/contracts/.env.example @@ -38,6 +38,7 @@ APPEND_METADATA_MAIN=true export PRIVATE_KEY_LOCAL=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + # Don't use these!!! Use Foundry's account feature (see below), which avoids writing down private # keys, avoiding all risk of them leaking. @@ -74,4 +75,15 @@ export ANVIL_PORT=8545 export BUNDLER_PORT=3000 # Used in scripts/account_abstraction_demo.ts -export BUNDLER_LOCAL=http://127.0.0.1:3000 \ No newline at end of file +export BUNDLER_LOCAL=http://127.0.0.1:3000 + +#################################################################################################### +# Random Contract Configuration +#################################################################################################### + +# Security margin in blocks when posting a commitment +export PRECOMMIT_DELAY_BLOCKS=21600 + +# Owner of the Random contract +# For this example, we are using test account 0 as the owner +export RANDOM_OWNER=0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 diff --git a/contracts/deployments/anvil/random/abis.json b/contracts/deployments/anvil/random/abis.json index 98b6473587..dae6b8ec37 100644 --- a/contracts/deployments/anvil/random/abis.json +++ b/contracts/deployments/anvil/random/abis.json @@ -4,17 +4,27 @@ "type": "constructor", "inputs": [ { - "name": "_publicKey", + "name": "_owner", + "type": "address", + "internalType": "address" + }, + { + "name": "_drandPublicKey", "type": "uint256[4]", "internalType": "uint256[4]" }, { - "name": "_genesisTimestamp", + "name": "_drandGenesisTimestampSeconds", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_drandPeriodSeconds", "type": "uint256", "internalType": "uint256" }, { - "name": "_period", + "name": "_precommitDelayBlocks", "type": "uint256", "internalType": "uint256" } @@ -23,7 +33,7 @@ }, { "type": "function", - "name": "DRAND_DELAY", + "name": "DRAND_DELAY_SECONDS", "inputs": [], "outputs": [ { @@ -36,20 +46,20 @@ }, { "type": "function", - "name": "DST", + "name": "DRAND_GENESIS_TIMESTAMP_SECONDS", "inputs": [], "outputs": [ { "name": "", - "type": "bytes", - "internalType": "bytes" + "type": "uint256", + "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", - "name": "PRECOMMIT_DELAY", + "name": "DRAND_PERIOD_SECONDS", "inputs": [], "outputs": [ { @@ -62,27 +72,34 @@ }, { "type": "function", - "name": "genesisTimestamp", + "name": "DST", "inputs": [], "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "bytes", + "internalType": "bytes" } ], "stateMutability": "view" }, { "type": "function", - "name": "getRevealedValue", - "inputs": [ + "name": "MIN_PRECOMMIT_TIME_SECONDS", + "inputs": [], + "outputs": [ { - "name": "blockNumber", + "name": "", "type": "uint256", "internalType": "uint256" } ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "PRECOMMIT_DELAY_BLOCKS", + "inputs": [], "outputs": [ { "name": "", @@ -94,14 +111,21 @@ }, { "type": "function", - "name": "nextValidTimestamp", - "inputs": [ + "name": "drandPK0", + "inputs": [], + "outputs": [ { - "name": "timestamp", + "name": "", "type": "uint256", "internalType": "uint256" } ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandPK1", + "inputs": [], "outputs": [ { "name": "", @@ -113,20 +137,20 @@ }, { "type": "function", - "name": "owner", + "name": "drandPK2", "inputs": [], "outputs": [ { "name": "", - "type": "address", - "internalType": "address" + "type": "uint256", + "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", - "name": "period", + "name": "drandPK3", "inputs": [], "outputs": [ { @@ -139,46 +163,67 @@ }, { "type": "function", - "name": "postCommitment", + "name": "drandRandomness", "inputs": [ { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - }, + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ { - "name": "commitmentHash", + "name": "randomness", "type": "bytes32", "internalType": "bytes32" } ], - "outputs": [], - "stateMutability": "nonpayable" + "stateMutability": "view" }, { "type": "function", - "name": "postDrand", + "name": "getDrand", "inputs": [ { "name": "round", "type": "uint64", "internalType": "uint64" - }, + } + ], + "outputs": [ { - "name": "signature", - "type": "uint256[2]", - "internalType": "uint256[2]" + "name": "", + "type": "bytes32", + "internalType": "bytes32" } ], - "outputs": [], - "stateMutability": "nonpayable" + "stateMutability": "view" }, { "type": "function", - "name": "publicKey", + "name": "getRevealedValue", "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [ { "name": "", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidTimestamp", + "inputs": [ + { + "name": "timestamp", "type": "uint256", "internalType": "uint256" } @@ -194,30 +239,60 @@ }, { "type": "function", - "name": "random", + "name": "owner", "inputs": [], "outputs": [ { "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "postCommitment", + "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "commitmentHash", "type": "bytes32", "internalType": "bytes32" } ], - "stateMutability": "view" + "outputs": [], + "stateMutability": "nonpayable" }, { "type": "function", - "name": "randomForTimestamp", + "name": "postDrand", "inputs": [ { - "name": "timestamp", - "type": "uint256", - "internalType": "uint256" + "name": "round", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" } ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "random", + "inputs": [], "outputs": [ { - "name": "", + "name": "randomValue", "type": "bytes32", "internalType": "bytes32" } @@ -226,17 +301,17 @@ }, { "type": "function", - "name": "randomness", + "name": "randomForTimestamp", "inputs": [ { - "name": "round", - "type": "uint64", - "internalType": "uint64" + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" } ], "outputs": [ { - "name": "randomness", + "name": "", "type": "bytes32", "internalType": "bytes32" } @@ -256,13 +331,13 @@ "inputs": [ { "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" + "type": "uint128", + "internalType": "uint128" }, { "name": "revealedValue", - "type": "uint256", - "internalType": "uint256" + "type": "uint128", + "internalType": "uint128" } ], "outputs": [], @@ -281,21 +356,40 @@ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "unsafeGetDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "unsafeGetRevealedValue", "inputs": [ { "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" + "type": "uint128", + "internalType": "uint128" } ], "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "uint128", + "internalType": "uint128" } ], "stateMutability": "view" @@ -306,9 +400,9 @@ "inputs": [ { "name": "blockNumber", - "type": "uint256", + "type": "uint128", "indexed": true, - "internalType": "uint256" + "internalType": "uint128" }, { "name": "commitment", @@ -319,6 +413,25 @@ ], "anonymous": false }, + { + "type": "event", + "name": "DrandRandomnessPosted", + "inputs": [ + { + "name": "round", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "randomness", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, { "type": "event", "name": "OwnershipTransferred", @@ -344,15 +457,15 @@ "inputs": [ { "name": "blockNumber", - "type": "uint256", + "type": "uint128", "indexed": true, - "internalType": "uint256" + "internalType": "uint128" }, { "name": "revealedValue", - "type": "uint256", + "type": "uint128", "indexed": false, - "internalType": "uint256" + "internalType": "uint128" } ], "anonymous": false diff --git a/contracts/deployments/anvil/random/abis.ts b/contracts/deployments/anvil/random/abis.ts index 48835e8f78..24f08424a9 100644 --- a/contracts/deployments/anvil/random/abis.ts +++ b/contracts/deployments/anvil/random/abis.ts @@ -9,17 +9,27 @@ const contractToAbi = ({ "type": "constructor", "inputs": [ { - "name": "_publicKey", + "name": "_owner", + "type": "address", + "internalType": "address" + }, + { + "name": "_drandPublicKey", "type": "uint256[4]", "internalType": "uint256[4]" }, { - "name": "_genesisTimestamp", + "name": "_drandGenesisTimestampSeconds", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_drandPeriodSeconds", "type": "uint256", "internalType": "uint256" }, { - "name": "_period", + "name": "_precommitDelayBlocks", "type": "uint256", "internalType": "uint256" } @@ -28,7 +38,7 @@ const contractToAbi = ({ }, { "type": "function", - "name": "DRAND_DELAY", + "name": "DRAND_DELAY_SECONDS", "inputs": [], "outputs": [ { @@ -41,20 +51,20 @@ const contractToAbi = ({ }, { "type": "function", - "name": "DST", + "name": "DRAND_GENESIS_TIMESTAMP_SECONDS", "inputs": [], "outputs": [ { "name": "", - "type": "bytes", - "internalType": "bytes" + "type": "uint256", + "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", - "name": "PRECOMMIT_DELAY", + "name": "DRAND_PERIOD_SECONDS", "inputs": [], "outputs": [ { @@ -67,27 +77,34 @@ const contractToAbi = ({ }, { "type": "function", - "name": "genesisTimestamp", + "name": "DST", "inputs": [], "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "bytes", + "internalType": "bytes" } ], "stateMutability": "view" }, { "type": "function", - "name": "getRevealedValue", - "inputs": [ + "name": "MIN_PRECOMMIT_TIME_SECONDS", + "inputs": [], + "outputs": [ { - "name": "blockNumber", + "name": "", "type": "uint256", "internalType": "uint256" } ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "PRECOMMIT_DELAY_BLOCKS", + "inputs": [], "outputs": [ { "name": "", @@ -99,14 +116,21 @@ const contractToAbi = ({ }, { "type": "function", - "name": "nextValidTimestamp", - "inputs": [ + "name": "drandPK0", + "inputs": [], + "outputs": [ { - "name": "timestamp", + "name": "", "type": "uint256", "internalType": "uint256" } ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandPK1", + "inputs": [], "outputs": [ { "name": "", @@ -118,20 +142,20 @@ const contractToAbi = ({ }, { "type": "function", - "name": "owner", + "name": "drandPK2", "inputs": [], "outputs": [ { "name": "", - "type": "address", - "internalType": "address" + "type": "uint256", + "internalType": "uint256" } ], "stateMutability": "view" }, { "type": "function", - "name": "period", + "name": "drandPK3", "inputs": [], "outputs": [ { @@ -144,46 +168,67 @@ const contractToAbi = ({ }, { "type": "function", - "name": "postCommitment", + "name": "drandRandomness", "inputs": [ { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - }, + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ { - "name": "commitmentHash", + "name": "randomness", "type": "bytes32", "internalType": "bytes32" } ], - "outputs": [], - "stateMutability": "nonpayable" + "stateMutability": "view" }, { "type": "function", - "name": "postDrand", + "name": "getDrand", "inputs": [ { "name": "round", "type": "uint64", "internalType": "uint64" - }, + } + ], + "outputs": [ { - "name": "signature", - "type": "uint256[2]", - "internalType": "uint256[2]" + "name": "", + "type": "bytes32", + "internalType": "bytes32" } ], - "outputs": [], - "stateMutability": "nonpayable" + "stateMutability": "view" }, { "type": "function", - "name": "publicKey", + "name": "getRevealedValue", "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [ { "name": "", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidTimestamp", + "inputs": [ + { + "name": "timestamp", "type": "uint256", "internalType": "uint256" } @@ -199,30 +244,60 @@ const contractToAbi = ({ }, { "type": "function", - "name": "random", + "name": "owner", "inputs": [], "outputs": [ { "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "postCommitment", + "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "commitmentHash", "type": "bytes32", "internalType": "bytes32" } ], - "stateMutability": "view" + "outputs": [], + "stateMutability": "nonpayable" }, { "type": "function", - "name": "randomForTimestamp", + "name": "postDrand", "inputs": [ { - "name": "timestamp", - "type": "uint256", - "internalType": "uint256" + "name": "round", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" } ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "random", + "inputs": [], "outputs": [ { - "name": "", + "name": "randomValue", "type": "bytes32", "internalType": "bytes32" } @@ -231,17 +306,17 @@ const contractToAbi = ({ }, { "type": "function", - "name": "randomness", + "name": "randomForTimestamp", "inputs": [ { - "name": "round", - "type": "uint64", - "internalType": "uint64" + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" } ], "outputs": [ { - "name": "randomness", + "name": "", "type": "bytes32", "internalType": "bytes32" } @@ -261,13 +336,13 @@ const contractToAbi = ({ "inputs": [ { "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" + "type": "uint128", + "internalType": "uint128" }, { "name": "revealedValue", - "type": "uint256", - "internalType": "uint256" + "type": "uint128", + "internalType": "uint128" } ], "outputs": [], @@ -286,21 +361,40 @@ const contractToAbi = ({ "outputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "unsafeGetDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, { "type": "function", "name": "unsafeGetRevealedValue", "inputs": [ { "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" + "type": "uint128", + "internalType": "uint128" } ], "outputs": [ { "name": "", - "type": "uint256", - "internalType": "uint256" + "type": "uint128", + "internalType": "uint128" } ], "stateMutability": "view" @@ -311,9 +405,9 @@ const contractToAbi = ({ "inputs": [ { "name": "blockNumber", - "type": "uint256", + "type": "uint128", "indexed": true, - "internalType": "uint256" + "internalType": "uint128" }, { "name": "commitment", @@ -324,6 +418,25 @@ const contractToAbi = ({ ], "anonymous": false }, + { + "type": "event", + "name": "DrandRandomnessPosted", + "inputs": [ + { + "name": "round", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "randomness", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, { "type": "event", "name": "OwnershipTransferred", @@ -349,15 +462,15 @@ const contractToAbi = ({ "inputs": [ { "name": "blockNumber", - "type": "uint256", + "type": "uint128", "indexed": true, - "internalType": "uint256" + "internalType": "uint128" }, { "name": "revealedValue", - "type": "uint256", + "type": "uint128", "indexed": false, - "internalType": "uint256" + "internalType": "uint128" } ], "anonymous": false @@ -531,7 +644,7 @@ const aliasToContract = ({ }) as const export const deployment = ({ - "Random": "0x9ACE2eE177EB91eEed9591ea50Cb903d551DD0f9" + "Random": "0xd6061015153788Bdc9FFe2a73acaa3ea0Cb253d6" }) as const export type ContractToAbi = typeof contractToAbi diff --git a/contracts/deployments/anvil/random/deployment.json b/contracts/deployments/anvil/random/deployment.json index 6c2374d088..278ffbfc0f 100644 --- a/contracts/deployments/anvil/random/deployment.json +++ b/contracts/deployments/anvil/random/deployment.json @@ -1,3 +1,3 @@ { - "Random": "0x9ACE2eE177EB91eEed9591ea50Cb903d551DD0f9" + "Random": "0xd6061015153788Bdc9FFe2a73acaa3ea0Cb253d6" } \ No newline at end of file diff --git a/contracts/deployments/happy-sepolia/random/abis.json b/contracts/deployments/happy-sepolia/random/abis.json index b80a6d1bf4..31b74da9ff 100644 --- a/contracts/deployments/happy-sepolia/random/abis.json +++ b/contracts/deployments/happy-sepolia/random/abis.json @@ -4,27 +4,27 @@ "type": "constructor", "inputs": [ { - "name": "_publicKey", - "type": "uint256[4]", - "internalType": "uint256[4]" + "name": "_owner", + "type": "address", + "internalType": "address" }, { - "name": "_genesisTimestamp", - "type": "uint256", - "internalType": "uint256" + "name": "_drandPublicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" }, { - "name": "_period", + "name": "_drandGenesisTimestampSeconds", "type": "uint256", "internalType": "uint256" }, { - "name": "_happyGenesisBlock", + "name": "_drandPeriodSeconds", "type": "uint256", "internalType": "uint256" }, { - "name": "_happyTimeBlock", + "name": "_precommitDelayBlocks", "type": "uint256", "internalType": "uint256" } @@ -33,7 +33,7 @@ }, { "type": "function", - "name": "DRAND_DELAY", + "name": "DRAND_DELAY_SECONDS", "inputs": [], "outputs": [ { @@ -46,7 +46,7 @@ }, { "type": "function", - "name": "DRAND_GENESIS_TIMESTAMP", + "name": "DRAND_GENESIS_TIMESTAMP_SECONDS", "inputs": [], "outputs": [ { @@ -59,7 +59,7 @@ }, { "type": "function", - "name": "DRAND_PERIOD", + "name": "DRAND_PERIOD_SECONDS", "inputs": [], "outputs": [ { @@ -85,20 +85,7 @@ }, { "type": "function", - "name": "HAPPY_GENESIS_BLOCK", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "HAPPY_TIME_BLOCK", + "name": "MIN_PRECOMMIT_TIME_SECONDS", "inputs": [], "outputs": [ { @@ -111,7 +98,7 @@ }, { "type": "function", - "name": "PRECOMMIT_DELAY", + "name": "PRECOMMIT_DELAY_BLOCKS", "inputs": [], "outputs": [ { @@ -198,25 +185,6 @@ ], "stateMutability": "view" }, - { - "type": "function", - "name": "nextValidBlock", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "nextValidTimestamp", @@ -298,25 +266,6 @@ ], "stateMutability": "view" }, - { - "type": "function", - "name": "randomForBlock", - "inputs": [ - { - "name": "blockNumber", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "bytes32", - "internalType": "bytes32" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "randomForTimestamp", diff --git a/contracts/deployments/happy-sepolia/random/abis.ts b/contracts/deployments/happy-sepolia/random/abis.ts index fddcfd128a..b40752b135 100644 --- a/contracts/deployments/happy-sepolia/random/abis.ts +++ b/contracts/deployments/happy-sepolia/random/abis.ts @@ -2,666 +2,616 @@ import type { MapTuple, ObjectFromTuples, UnionToTuple } from "@happy.tech/common" import type { Address } from "viem" -const contractToAbi = { - Random: [ - { - type: "constructor", - inputs: [ - { - name: "_publicKey", - type: "uint256[4]", - internalType: "uint256[4]", - }, - { - name: "_genesisTimestamp", - type: "uint256", - internalType: "uint256", - }, - { - name: "_period", - type: "uint256", - internalType: "uint256", - }, - { - name: "_happyGenesisBlock", - type: "uint256", - internalType: "uint256", - }, - { - name: "_happyTimeBlock", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "DRAND_DELAY", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "DRAND_GENESIS_TIMESTAMP", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "DRAND_PERIOD", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "DST", - inputs: [], - outputs: [ - { - name: "", - type: "bytes", - internalType: "bytes", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "HAPPY_GENESIS_BLOCK", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "HAPPY_TIME_BLOCK", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "PRECOMMIT_DELAY", - inputs: [], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "drandPublicKey", - inputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "drandRandomness", - inputs: [ - { - name: "round", - type: "uint64", - internalType: "uint64", - }, - ], - outputs: [ - { - name: "randomness", - type: "bytes32", - internalType: "bytes32", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "getDrand", - inputs: [ - { - name: "round", - type: "uint64", - internalType: "uint64", - }, - ], - outputs: [ - { - name: "", - type: "bytes32", - internalType: "bytes32", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "getRevealedValue", - inputs: [ - { - name: "blockNumber", - type: "uint128", - internalType: "uint128", - }, - ], - outputs: [ - { - name: "", - type: "uint128", - internalType: "uint128", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "nextValidBlock", - inputs: [ - { - name: "blockNumber", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "nextValidTimestamp", - inputs: [ - { - name: "timestamp", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "uint256", - internalType: "uint256", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "owner", - inputs: [], - outputs: [ - { - name: "", - type: "address", - internalType: "address", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "postCommitment", - inputs: [ - { - name: "blockNumber", - type: "uint128", - internalType: "uint128", - }, - { - name: "commitmentHash", - type: "bytes32", - internalType: "bytes32", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "postDrand", - inputs: [ - { - name: "round", - type: "uint64", - internalType: "uint64", - }, - { - name: "signature", - type: "uint256[2]", - internalType: "uint256[2]", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "random", - inputs: [], - outputs: [ - { - name: "randomValue", - type: "bytes32", - internalType: "bytes32", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "randomForBlock", - inputs: [ - { - name: "blockNumber", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "bytes32", - internalType: "bytes32", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "randomForTimestamp", - inputs: [ - { - name: "timestamp", - type: "uint256", - internalType: "uint256", - }, - ], - outputs: [ - { - name: "", - type: "bytes32", - internalType: "bytes32", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "renounceOwnership", - inputs: [], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "revealValue", - inputs: [ - { - name: "blockNumber", - type: "uint128", - internalType: "uint128", - }, - { - name: "revealedValue", - type: "uint128", - internalType: "uint128", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "transferOwnership", - inputs: [ - { - name: "newOwner", - type: "address", - internalType: "address", - }, - ], - outputs: [], - stateMutability: "nonpayable", - }, - { - type: "function", - name: "unsafeGetDrand", - inputs: [ - { - name: "round", - type: "uint64", - internalType: "uint64", - }, - ], - outputs: [ - { - name: "", - type: "bytes32", - internalType: "bytes32", - }, - ], - stateMutability: "view", - }, - { - type: "function", - name: "unsafeGetRevealedValue", - inputs: [ - { - name: "blockNumber", - type: "uint128", - internalType: "uint128", - }, - ], - outputs: [ - { - name: "", - type: "uint128", - internalType: "uint128", - }, - ], - stateMutability: "view", - }, - { - type: "event", - name: "CommitmentPosted", - inputs: [ - { - name: "blockNumber", - type: "uint128", - indexed: true, - internalType: "uint128", - }, - { - name: "commitment", - type: "bytes32", - indexed: false, - internalType: "bytes32", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "DrandRandomnessPosted", - inputs: [ - { - name: "round", - type: "uint64", - indexed: true, - internalType: "uint64", - }, - { - name: "randomness", - type: "bytes32", - indexed: false, - internalType: "bytes32", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "OwnershipTransferred", - inputs: [ - { - name: "previousOwner", - type: "address", - indexed: true, - internalType: "address", - }, - { - name: "newOwner", - type: "address", - indexed: true, - internalType: "address", - }, - ], - anonymous: false, - }, - { - type: "event", - name: "ValueRevealed", - inputs: [ - { - name: "blockNumber", - type: "uint128", - indexed: true, - internalType: "uint128", - }, - { - name: "revealedValue", - type: "uint128", - indexed: false, - internalType: "uint128", - }, - ], - anonymous: false, - }, - { - type: "error", - name: "BNAddFailed", - inputs: [ - { - name: "input", - type: "uint256[4]", - internalType: "uint256[4]", - }, - ], - }, - { - type: "error", - name: "CommitmentAlreadyExists", - inputs: [], - }, - { - type: "error", - name: "CommitmentTooLate", - inputs: [], - }, - { - type: "error", - name: "DrandNotAvailable", - inputs: [ - { - name: "round", - type: "uint64", - internalType: "uint64", - }, - ], - }, - { - type: "error", - name: "InvalidDSTLength", - inputs: [ - { - name: "dst", - type: "bytes", - internalType: "bytes", - }, - ], - }, - { - type: "error", - name: "InvalidFieldElement", - inputs: [ - { - name: "x", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "InvalidPublicKey", - inputs: [ - { - name: "publicKey", - type: "uint256[4]", - internalType: "uint256[4]", - }, - ], - }, - { - type: "error", - name: "InvalidReveal", - inputs: [], - }, - { - type: "error", - name: "InvalidSignature", - inputs: [ - { - name: "publicKey", - type: "uint256[4]", - internalType: "uint256[4]", - }, - { - name: "message", - type: "uint256[2]", - internalType: "uint256[2]", - }, - { - name: "signature", - type: "uint256[2]", - internalType: "uint256[2]", - }, - ], - }, - { - type: "error", - name: "MapToPointFailed", - inputs: [ - { - name: "noSqrt", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "ModExpFailed", - inputs: [ - { - name: "base", - type: "uint256", - internalType: "uint256", - }, - { - name: "exponent", - type: "uint256", - internalType: "uint256", - }, - { - name: "modulus", - type: "uint256", - internalType: "uint256", - }, - ], - }, - { - type: "error", - name: "NoCommitmentFound", - inputs: [], - }, - { - type: "error", - name: "OwnableInvalidOwner", - inputs: [ - { - name: "owner", - type: "address", - internalType: "address", - }, - ], - }, - { - type: "error", - name: "OwnableUnauthorizedAccount", - inputs: [ - { - name: "account", - type: "address", - internalType: "address", - }, - ], - }, - { - type: "error", - name: "RevealMustBeOnExactBlock", - inputs: [], - }, - { - type: "error", - name: "RevealedValueNotAvailable", - inputs: [], - }, - ], -} as const +const contractToAbi = ({ + "Random": [ + { + "type": "constructor", + "inputs": [ + { + "name": "_owner", + "type": "address", + "internalType": "address" + }, + { + "name": "_drandPublicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "_drandGenesisTimestampSeconds", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_drandPeriodSeconds", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_precommitDelayBlocks", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "DRAND_DELAY_SECONDS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DRAND_GENESIS_TIMESTAMP_SECONDS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DRAND_PERIOD_SECONDS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DST", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "MIN_PRECOMMIT_TIME_SECONDS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "PRECOMMIT_DELAY_BLOCKS", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandPublicKey", + "inputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "drandRandomness", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "randomness", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [ + { + "name": "", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nextValidTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "postCommitment", + "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "commitmentHash", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "postDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "random", + "inputs": [], + "outputs": [ + { + "name": "randomValue", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "randomForTimestamp", + "inputs": [ + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "renounceOwnership", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "revealValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "revealedValue", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferOwnership", + "inputs": [ + { + "name": "newOwner", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unsafeGetDrand", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "unsafeGetRevealedValue", + "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "internalType": "uint128" + } + ], + "outputs": [ + { + "name": "", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "view" + }, + { + "type": "event", + "name": "CommitmentPosted", + "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "indexed": true, + "internalType": "uint128" + }, + { + "name": "commitment", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DrandRandomnessPosted", + "inputs": [ + { + "name": "round", + "type": "uint64", + "indexed": true, + "internalType": "uint64" + }, + { + "name": "randomness", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "OwnershipTransferred", + "inputs": [ + { + "name": "previousOwner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "newOwner", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ValueRevealed", + "inputs": [ + { + "name": "blockNumber", + "type": "uint128", + "indexed": true, + "internalType": "uint128" + }, + { + "name": "revealedValue", + "type": "uint128", + "indexed": false, + "internalType": "uint128" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "BNAddFailed", + "inputs": [ + { + "name": "input", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, + { + "type": "error", + "name": "CommitmentAlreadyExists", + "inputs": [] + }, + { + "type": "error", + "name": "CommitmentTooLate", + "inputs": [] + }, + { + "type": "error", + "name": "DrandNotAvailable", + "inputs": [ + { + "name": "round", + "type": "uint64", + "internalType": "uint64" + } + ] + }, + { + "type": "error", + "name": "InvalidDSTLength", + "inputs": [ + { + "name": "dst", + "type": "bytes", + "internalType": "bytes" + } + ] + }, + { + "type": "error", + "name": "InvalidFieldElement", + "inputs": [ + { + "name": "x", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "InvalidPublicKey", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + } + ] + }, + { + "type": "error", + "name": "InvalidReveal", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidSignature", + "inputs": [ + { + "name": "publicKey", + "type": "uint256[4]", + "internalType": "uint256[4]" + }, + { + "name": "message", + "type": "uint256[2]", + "internalType": "uint256[2]" + }, + { + "name": "signature", + "type": "uint256[2]", + "internalType": "uint256[2]" + } + ] + }, + { + "type": "error", + "name": "MapToPointFailed", + "inputs": [ + { + "name": "noSqrt", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "ModExpFailed", + "inputs": [ + { + "name": "base", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "exponent", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "modulus", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "NoCommitmentFound", + "inputs": [] + }, + { + "type": "error", + "name": "OwnableInvalidOwner", + "inputs": [ + { + "name": "owner", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "OwnableUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "RevealMustBeOnExactBlock", + "inputs": [] + }, + { + "type": "error", + "name": "RevealedValueNotAvailable", + "inputs": [] + } + ] +} +) as const -const aliasToContract = { - Random: "Random", -} as const +const aliasToContract = ({ + "Random": "Random" +}) as const -export const deployment = { - Random: "0x5FbDB2315678afecb367f032d93F642f64180aa3", -} as const +export const deployment = ({ + "Random": "0x02d41cE77B17f499300eD97C6AD19905c36ba556" +}) as const export type ContractToAbi = typeof contractToAbi export type AliasToContract = typeof aliasToContract diff --git a/contracts/deployments/happy-sepolia/random/deployment.json b/contracts/deployments/happy-sepolia/random/deployment.json index 260d25ee58..0f0559d594 100644 --- a/contracts/deployments/happy-sepolia/random/deployment.json +++ b/contracts/deployments/happy-sepolia/random/deployment.json @@ -1,3 +1,3 @@ { - "Random": "0x51745e910FaD45A6cA620bCc7a7fAB7683B142e5" + "Random": "0x02d41cE77B17f499300eD97C6AD19905c36ba556" } \ No newline at end of file diff --git a/contracts/src/deploy/DeployRandom.s.sol b/contracts/src/deploy/DeployRandom.s.sol index 004ab84fa0..dfbe8fbc5f 100644 --- a/contracts/src/deploy/DeployRandom.s.sol +++ b/contracts/src/deploy/DeployRandom.s.sol @@ -18,9 +18,9 @@ contract DeployRandom is BaseDeployScript { // Drand evmnet public key uint256[4] public drandPublicKey; // Drand evmnet genesis time (2024-01-29 00:11:15 UTC) - uint256 public constant DRAND_GENESIS_TIMESTAMP = 1727521075; + uint256 public constant DRAND_GENESIS_TIMESTAMP_SECONDS = 1727521075; // Drand evmnet period (3 seconds) - uint256 public constant DRAND_PERIOD = 3; + uint256 public constant DRAND_PERIOD_SECONDS = 3; constructor() { drandPublicKey = [ @@ -32,10 +32,14 @@ contract DeployRandom is BaseDeployScript { } function deploy() internal override { + uint256 precommitDelayBlocks = vm.envUint("PRECOMMIT_DELAY_BLOCKS"); + address owner = vm.envAddress("RANDOM_OWNER"); (address _random,) = deployDeterministic( "Random", type(Random).creationCode, - abi.encode(drandPublicKey, DRAND_GENESIS_TIMESTAMP, DRAND_PERIOD), + abi.encode( + owner, drandPublicKey, DRAND_GENESIS_TIMESTAMP_SECONDS, DRAND_PERIOD_SECONDS, precommitDelayBlocks + ), DEPLOYMENT_SALT ); random = Random(_random); diff --git a/contracts/src/randomness/Drand.sol b/contracts/src/randomness/Drand.sol index 1c9ca30ba4..d3ab7df7d3 100644 --- a/contracts/src/randomness/Drand.sol +++ b/contracts/src/randomness/Drand.sol @@ -6,69 +6,96 @@ import {BLS} from "bls-bn254/BLS.sol"; contract Drand { bytes public constant DST = bytes("BLS_SIG_BN254G1_XMD:KECCAK-256_SVDW_RO_NUL_"); - uint256[4] public publicKey; - uint256 public genesisTimestamp; - uint256 public period; - mapping(uint64 round => bytes32 randomness) public randomness; + uint256 public immutable DRAND_PK_0; + uint256 public immutable DRAND_PK_1; + uint256 public immutable DRAND_PK_2; + uint256 public immutable DRAND_PK_3; + uint256 public immutable DRAND_GENESIS_TIMESTAMP_SECONDS; + uint256 public immutable DRAND_PERIOD_SECONDS; + mapping(uint64 round => bytes32 randomness) public drandRandomness; + + event DrandRandomnessPosted(uint64 indexed round, bytes32 randomness); error InvalidPublicKey(uint256[4] publicKey); error InvalidSignature(uint256[4] publicKey, uint256[2] message, uint256[2] signature); error DrandNotAvailable(uint64 round); - constructor(uint256[4] memory _publicKey, uint256 _genesisTimestamp, uint256 _period) { - if (!BLS.isValidPublicKey(_publicKey)) { - revert InvalidPublicKey(_publicKey); + constructor(uint256[4] memory _drandPublicKey, uint256 _drandGenesisTimestampSeconds, uint256 _drandPeriodSeconds) { + if (!BLS.isValidPublicKey(_drandPublicKey)) { + revert InvalidPublicKey(_drandPublicKey); } - publicKey = _publicKey; - genesisTimestamp = _genesisTimestamp; - period = _period; + DRAND_PK_0 = _drandPublicKey[0]; + DRAND_PK_1 = _drandPublicKey[1]; + DRAND_PK_2 = _drandPublicKey[2]; + DRAND_PK_3 = _drandPublicKey[3]; + DRAND_GENESIS_TIMESTAMP_SECONDS = _drandGenesisTimestampSeconds; + DRAND_PERIOD_SECONDS = _drandPeriodSeconds; } + /** + * @notice Posts a new Drand signature for a given drand round. + */ function postDrand(uint64 round, uint256[2] memory signature) external { // Encode round for hash-to-point bytes memory hashedRoundBytes = new bytes(32); + + // hashedRoundBytes = keccak256(abi.encodePacked(round)) — not valid solidity syntax assembly { mstore(0x00, round) let hashedRound := keccak256(0x18, 0x08) // hash the last 8 bytes (uint64) of `round` mstore(add(0x20, hashedRoundBytes), hashedRound) } uint256[2] memory message = BLS.hashToPoint(DST, hashedRoundBytes); - // NB: Always check that the signature is a valid signature (a valid G1 point on the curve)! - bool isValidSignature = BLS.isValidSignature(signature); - if (!isValidSignature) { - revert InvalidSignature(publicKey, message, signature); + if (!BLS.isValidSignature(signature)) { + revert InvalidSignature([DRAND_PK_0, DRAND_PK_1, DRAND_PK_2, DRAND_PK_3], message, signature); } // Verify the signature over the message using the public key - (bool pairingSuccess, bool callSuccess) = BLS.verifySingle(signature, publicKey, message); + (bool pairingSuccess, bool callSuccess) = + BLS.verifySingle(signature, [DRAND_PK_0, DRAND_PK_1, DRAND_PK_2, DRAND_PK_3], message); if (!pairingSuccess || !callSuccess) { - revert InvalidSignature(publicKey, message, signature); + revert InvalidSignature([DRAND_PK_0, DRAND_PK_1, DRAND_PK_2, DRAND_PK_3], message, signature); } bytes32 roundRandomness = keccak256(abi.encode(signature)); - randomness[round] = roundRandomness; + drandRandomness[round] = roundRandomness; + emit DrandRandomnessPosted(round, roundRandomness); } - function _unsafeGetDrand(uint64 round) internal view returns (bytes32) { - return randomness[round]; + /** + * @notice Retrieves the randomness value for a specific drand round. + * This function does not revert if the randomness value is not available. Instead, it returns 0. + */ + function unsafeGetDrand(uint64 round) public view returns (bytes32) { + return drandRandomness[round]; } - function _getDrand(uint64 round) internal view returns (bytes32) { - if (randomness[round] == 0) { + /** + * @notice Retrieves the randomness value for a specific drand round. + * This function reverts if the randomness value is not available. + */ + function getDrand(uint64 round) public view returns (bytes32) { + if (drandRandomness[round] == 0) { revert DrandNotAvailable(round); } - return randomness[round]; + return drandRandomness[round]; } + /** + * @notice Returns the latest drand value at the given timestamp. + */ function _getDrandAtTimestamp(uint256 timestamp) internal view returns (bytes32) { - uint64 round = uint64((timestamp - genesisTimestamp) / period); - return _getDrand(round); + uint64 round = uint64((timestamp - DRAND_GENESIS_TIMESTAMP_SECONDS) / DRAND_PERIOD_SECONDS); + return getDrand(round); } - function _nextValidTimestamp(uint256 timestamp) internal view returns (uint256) { - return timestamp + (period - (timestamp % period)); + /** + * @notice Returns the timestamp in which a new drand value will be available after the given timestamp. + */ + function _nextValidDrandTimestamp(uint256 timestamp) internal view returns (uint256) { + return timestamp + (DRAND_PERIOD_SECONDS - (timestamp % DRAND_PERIOD_SECONDS)); } } diff --git a/contracts/src/randomness/Random.sol b/contracts/src/randomness/Random.sol index a5ff839c07..3f3721542f 100644 --- a/contracts/src/randomness/Random.sol +++ b/contracts/src/randomness/Random.sol @@ -5,24 +5,65 @@ import {RandomCommitment} from "./RandomCommitment.sol"; import {Drand} from "./Drand.sol"; contract Random is RandomCommitment, Drand { - uint256 public constant DRAND_DELAY = 6; + /* + * The amount of time in seconds by which we delay reading the values from the Drand network. + * + * This is necessary, because whenever a Drand value is generated at time T, it is not possible to guarantee it + * will be posted on a block with timestamp T (even if such a block exists) because of network delays. + */ + uint256 public constant DRAND_DELAY_SECONDS = 2; - constructor(uint256[4] memory _publicKey, uint256 _genesisTimestamp, uint256 _period) - RandomCommitment() - Drand(_publicKey, _genesisTimestamp, _period) + /** + * The minimum amount of time (in seconds) that commitments to future Drand randomness must be made + * in advance. This delay is relative to Drand timestamp (so DRAND_DELAY must also be added). + * + * This is used in the computation of nextValidTimestamp — always use that function to get a + * lower bound on the timestamp to commit to. + * + * This is necessary to enable independent detection of sequencer delays which could signify that the + * sequencer is waiting to know the Drand randomness before including a commitment to future Drand randomness. + */ + uint256 public constant MIN_PRECOMMIT_TIME_SECONDS = 3; + + constructor( + address _owner, + uint256[4] memory _drandPublicKey, + uint256 _drandGenesisTimestampSeconds, + uint256 _drandPeriodSeconds, + uint256 _precommitDelayBlocks + ) + RandomCommitment(_owner, _precommitDelayBlocks) + Drand(_drandPublicKey, _drandGenesisTimestampSeconds, _drandPeriodSeconds) {} - function random() external view returns (bytes32) { - bytes32 drand = _getDrandAtTimestamp(block.timestamp - DRAND_DELAY); - uint256 revealedValue = getRevealedValue(uint128(block.number)); - return keccak256(abi.encodePacked(drand, revealedValue)); + /** + * @notice Returns a safe random bytes32 value that changes every block + * @dev The random value is generated from the drand randomness at the timestamp when the block was generated + * and the revealed value at the current block number. + */ + function random() external view returns (bytes32 randomValue) { + bytes32 drand = _getDrandAtTimestamp(uint128(block.timestamp - DRAND_DELAY_SECONDS)); + uint128 revealedValue = getRevealedValue(uint128(block.number)); + randomValue = keccak256(abi.encodePacked(drand, revealedValue)); } + /** + * @notice Returns the latest drand randomness at the given timestamp. + * @dev The drand randomness associated with the timestamp when the block was generated will differ + * from the one provided directly by the drand network. + * This discrepancy exists because we introduce a security margin (DRAND_DELAY seconds) to ensure + * the drand value can be safely included in a block. + */ function randomForTimestamp(uint256 timestamp) external view returns (bytes32) { - return _getDrandAtTimestamp(timestamp); + return _getDrandAtTimestamp(timestamp - DRAND_DELAY_SECONDS); } + /** + * @notice Returns the next timestamp where the drand randomness is guaranteed to be unknown. + * This ensures that at the returned timestamp, no one can have prior knowledge of the drand randomness, + * maintaining its unpredictability. + */ function nextValidTimestamp(uint256 timestamp) public view returns (uint256) { - return _nextValidTimestamp(timestamp); + return _nextValidDrandTimestamp(timestamp + MIN_PRECOMMIT_TIME_SECONDS - 1) + DRAND_DELAY_SECONDS; } } diff --git a/contracts/src/randomness/RandomCommitment.sol b/contracts/src/randomness/RandomCommitment.sol index cb3950d70f..46477f31cc 100644 --- a/contracts/src/randomness/RandomCommitment.sol +++ b/contracts/src/randomness/RandomCommitment.sol @@ -16,7 +16,7 @@ contract RandomCommitment is Ownable { * For more details, please refer to: * https://specs.optimism.io/protocol/configurability.html#sequencing-window-size */ - uint256 public immutable PRECOMMIT_DELAY = 21600; + uint256 public immutable PRECOMMIT_DELAY_BLOCKS; CurrentReveal private currentReveal; mapping(uint128 blockNumber => bytes32) private commitments; @@ -31,13 +31,15 @@ contract RandomCommitment is Ownable { error InvalidReveal(); error RevealedValueNotAvailable(); - constructor() Ownable(msg.sender) {} + constructor(address _owner, uint256 _precommit_delay_blocks) Ownable(_owner) { + PRECOMMIT_DELAY_BLOCKS = _precommit_delay_blocks; + } /** * @notice Posts a commitment for a specific block number. */ function postCommitment(uint128 blockNumber, bytes32 commitmentHash) external onlyOwner { - if (block.number + PRECOMMIT_DELAY > blockNumber) { + if (block.number + PRECOMMIT_DELAY_BLOCKS > blockNumber) { revert CommitmentTooLate(); }