Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 8 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,17 @@ docker-compose*
.dockerignore
docker-bake.hcl

# CI/CD
# CI/CD and tooling
.github/
.claude/
.gitlab-ci.yml
.travis.yml
atlantis.yaml

# JS tooling (not needed for Rust builds)
package.json
package-lock.json
k6/

# Logs
*.log
Expand Down
46 changes: 46 additions & 0 deletions .github/workflows/label-external-prs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# SECURITY: This workflow uses pull_request_target. Do NOT add actions/checkout
# with a PR-controlled ref: that would execute attacker code with write access
# to secrets. Only read pull_request metadata.
name: Label External PRs for Import

on:
pull_request_target:
types: [closed]
branches: [main]

permissions:
issues: write

concurrency:
group: label-external-pr-${{ github.event.pull_request.number }}
cancel-in-progress: false
# cancel-in-progress: false is correct — label ops are idempotent but partial
# cancellation could leave a PR unlabeled. Two simultaneous merges both hit
# `gh label create --force`; one wins, the other is a no-op.

jobs:
label:
# Skip unmerged closes and the automated upstream-sync squash PRs.
# Bot author AND sync branch are both checked as belt-and-suspenders;
# either alone is sufficient to identify a sync PR.
if: >-
github.repository == 'circlefin/arc-node' &&
github.event.pull_request.merged == true &&
!(github.event.pull_request.user.login == 'circle-github-action-bot' &&
github.event.pull_request.head.ref == 'sync/copybara-export')
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Ensure label exists and apply
env:
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
gh label create pending-import \
--repo "${REPO}" \
--color ededed \
--description "Merged PR awaiting reverse-sync to upstream" \
--force
gh pr edit "${PR_NUMBER}" --repo "${REPO}" --add-label pending-import
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ node_modules/
**/*.rs.bk
*.pdb

# Python-specific
__pycache__/
*.pyc

# Test artifacts
/test-results/
/coverage/
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,17 @@ Arc is an open EVM-compatible layer 1 built on [Malachite](https://github.com/ci
- 🗳️ **[Consensus](crates/malachite-app/README.md)** - Consensus binary and configuration
- More: see Arc [developer docs](https://docs.arc.network/arc/concepts/welcome-to-arc) for guides, APIs, and specs

## Run a Node
## Install and Run a Node

See [Installation — Build from Source](docs/installation.md#build-from-source) for how to build and install arc-node binaries, and [Running an Arc Node](docs/running-an-arc-node.md) for configuration and startup.
### Install

See [Installation](docs/installation.md) for how to obtain the Arc node
binaries or Docker images (pre-built, from source, or via Docker).

### Run

See [Running an Arc Node](docs/running-an-arc-node.md) for configuration and
startup (binaries or Docker Compose).

## Development

Expand Down
14 changes: 12 additions & 2 deletions arcup/arcup
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,16 @@ set -e
# WARNING: the SemVer pattern: major.minor.patch must be followed as we use it to determine if the script is up to date.
ARCUP_INSTALLER_VERSION="0.0.1"

REPO="circlefin/arc-node"
# TODO: set GPG key fingerprint once release signing is configured
REPO="${ARC_REPO:-circlefin/arc-node}"
if [[ -n "${ARC_REPO:-}" ]] && [[ ! "$ARC_REPO" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$ ]]; then
echo "error: invalid ARC_REPO format: '$ARC_REPO' (expected 'owner/repo')" >&2
exit 1
fi
# TODO: set GPG key fingerprint once release signing key is published
GPG_KEY_FINGERPRINT=""
GPG_KEYSERVER="keyserver.ubuntu.com"
BIN_DIR="${ARC_BIN_DIR:-$HOME/.arc/bin}"
# Self-update always uses the canonical repo to prevent hijack via ARC_REPO
ARCUP_BIN_URL="https://raw.githubusercontent.com/circlefin/arc-node/main/arcup/arcup"
ARCUP_BIN_PATH="$BIN_DIR/arcup"
CURL_HEADERS=()
Expand Down Expand Up @@ -333,6 +338,11 @@ main() {
check_installer_up_to_date

info "Installing arc-node binaries..."
info "Using repository: $REPO"

if [[ -z "$GPG_KEY_FINGERPRINT" ]]; then
warn "GPG signature verification is disabled — no release signing key configured"
fi

mkdir -p "$BIN_DIR"

Expand Down
4 changes: 2 additions & 2 deletions assets/localdev/genesis.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import fs from 'fs'
import { z } from 'zod'
import { parseEther, parseGwei, toHex, zeroAddress } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { createBuilderContext, buildGenesis, GenesisConfig, schemaGenesisConfig } from '../../scripts/genesis'
import { createBuilderContext, buildGenesis, GenesisConfig, schemaGenesisConfig, localdevFeeRecipient } from '../../scripts/genesis'
import { bigintReplacer } from '../../scripts/genesis/types'
import { LocalDevAccountCreator } from '../../scripts/genesis/AccountCreator'

Expand Down Expand Up @@ -73,7 +73,7 @@ const build = async (options: z.infer<typeof localBuilderOptionsSchema>) => {

const config: GenesisConfig = {
timestamp: 1763620028n,
coinbase: proxyAdmin.address,
coinbase: localdevFeeRecipient,
hardforks: {
zero3Block: 0,
...hardforks,
Expand Down
52 changes: 26 additions & 26 deletions assets/localdev/genesis.json

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

This directory contains Solidity contracts and tests for the Arc project, built with Foundry.

## Compiler choice for genesis-deployed contracts

**Forge is the canonical compiler** for every CREATE2-deployed contract in Arc genesis
(`Memo`, `Multicall3From`, `Denylist` impl, `ProtocolConfig` impl, `ValidatorRegistry`
impl, `PermissionedValidatorManager` impl, `GasGuzzler`, `TestToken`).

The genesis builder (`contracts/scripts/ArtifactHelper.s.sol`), all CREATE2-sensitive
tests (`tests/localdev/genesis.test.ts`) all read from `contracts/out/forge/`.
Hardhat's compile output is **not** consumed for any CREATE2-sensitive path.

## Foundry

Expand Down
4 changes: 2 additions & 2 deletions contracts/scripts/Addresses.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ library Addresses {
// ============ Predeployed Contracts ============
address internal constant DETERMINISTIC_DEPLOYER_PROXY = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
address internal constant MULTICALL3 = 0xcA11bde05977b3631167028862bE2a173976CA11;
address internal constant MULTICALL3_FROM = 0xEb7cc06E3D3b5F9F9a5fA2B31B477ff72bB9c8b6;
address internal constant MEMO = 0x9702466268ccF55eAB64cdf484d272Ac08d3b75b;
address internal constant MULTICALL3_FROM = 0x825F535677d346626cDE45D64cf89C2a426467e0;
address internal constant MEMO = 0xe4aa7Ed3585AEf598179f873086F75Fcd6D4b755;

// ============ Helpers ============

Expand Down
42 changes: 24 additions & 18 deletions contracts/scripts/ArtifactHelper.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@

pragma solidity ^0.8.29;

// ArtifactHelper — genesis builder entrypoint. Reads compiled contract bytecode and
// simulates CREATE2 deployments to produce the `alloc` entries baked into genesis.json.
//
// Forge is canonical for all CREATE2-deployed genesis contracts — do NOT switch this
// to read Hardhat's output.
import {Script, console} from "forge-std/Script.sol";
import {stdJson} from "forge-std/StdJson.sol";

Expand Down Expand Up @@ -154,11 +159,12 @@ contract ArtifactHelper is Script {
}

function deployArcNetworkContracts(string memory arcNetworkContractDir, address validatorRegistryProxyAddr) internal returns (string memory) {
// Reads Forge's flat `<Name>.sol/<Name>.json` layout via `.bytecode.object`.
// ProtocolConfig
address protocolConfig = deployDeterministicContract(
loadBytecode(
string.concat(arcNetworkContractDir, "protocol-config/ProtocolConfig.sol/ProtocolConfig.json"),
".bytecode"
string.concat(arcNetworkContractDir, "ProtocolConfig.sol/ProtocolConfig.json"),
".bytecode.object"
)
);
vm.serializeString("output", "ProtocolConfig", getJsonContractCode(address(protocolConfig)));
Expand All @@ -167,16 +173,16 @@ contract ArtifactHelper is Script {
address denylist = deployDeterministicContract(
loadBytecode(
string.concat(arcNetworkContractDir, "Denylist.sol/Denylist.json"),
".bytecode"
".bytecode.object"
)
);
vm.serializeString("output", "Denylist", getJsonContractCode(address(denylist)));

// ValidatorRegistry
address validatorRegistry = deployDeterministicContract(
loadBytecode(
string.concat(arcNetworkContractDir, "validator-manager/ValidatorRegistry.sol/ValidatorRegistry.json"),
".bytecode"
string.concat(arcNetworkContractDir, "ValidatorRegistry.sol/ValidatorRegistry.json"),
".bytecode.object"
)
);
vm.serializeString("output", "ValidatorRegistry", getJsonContractCode(address(validatorRegistry)));
Expand All @@ -185,8 +191,8 @@ contract ArtifactHelper is Script {
address proxy = deployDeterministicContract(
bytes.concat(
loadBytecode(
string.concat(arcNetworkContractDir, "proxy/AdminUpgradeableProxy.sol/AdminUpgradeableProxy.json"),
".bytecode"
string.concat(arcNetworkContractDir, "AdminUpgradeableProxy.sol/AdminUpgradeableProxy.json"),
".bytecode.object"
),
abi.encode(address(validatorRegistry), address(0x0000000000000000000000000000000000000001), hex"")
)
Expand All @@ -197,8 +203,8 @@ contract ArtifactHelper is Script {
address poaManager = deployDeterministicContract(
bytes.concat(
loadBytecode(
string.concat(arcNetworkContractDir, "validator-manager/PermissionedValidatorManager.sol/PermissionedValidatorManager.json"),
".bytecode"
string.concat(arcNetworkContractDir, "PermissionedValidatorManager.sol/PermissionedValidatorManager.json"),
".bytecode.object"
),
abi.encode(address(validatorRegistryProxyAddr))
)
Expand All @@ -208,35 +214,35 @@ contract ArtifactHelper is Script {
// GasGuzzler
address gasGuzzler = deployDeterministicContract(
loadBytecode(
string.concat(arcNetworkContractDir, "mocks/GasGuzzler.sol/GasGuzzler.json"),
".bytecode"
string.concat(arcNetworkContractDir, "GasGuzzler.sol/GasGuzzler.json"),
".bytecode.object"
)
);
vm.serializeString("output", "GasGuzzler", getJsonContractCode(address(gasGuzzler)));

// TestToken (ERC-20 for spammer load testing)
address testToken = deployDeterministicContract(
loadBytecode(
string.concat(arcNetworkContractDir, "mocks/TestToken.sol/TestToken.json"),
".bytecode"
string.concat(arcNetworkContractDir, "TestToken.sol/TestToken.json"),
".bytecode.object"
)
);
vm.serializeString("output", "TestToken", getJsonContractCode(address(testToken)));

// Memo
address memo = deployDeterministicContract(
loadBytecode(
string.concat(arcNetworkContractDir, "memo/Memo.sol/Memo.json"),
".bytecode"
string.concat(arcNetworkContractDir, "Memo.sol/Memo.json"),
".bytecode.object"
)
);
vm.serializeString("output", "Memo", getJsonContractCode(address(memo)));

// Multicall3From
address multicall3From = deployDeterministicContract(
loadBytecode(
string.concat(arcNetworkContractDir, "batch/Multicall3From.sol/Multicall3From.json"),
".bytecode"
string.concat(arcNetworkContractDir, "Multicall3From.sol/Multicall3From.json"),
".bytecode.object"
)
);
return vm.serializeString("output", "Multicall3From", getJsonContractCode(address(multicall3From)));
Expand Down Expand Up @@ -302,7 +308,7 @@ contract ArtifactHelper is Script {
vm.serializeString("output", "FiatTokenProxy", getJsonContractCode(fiatTokenProxyAddr));

// Deploy ArcNetwork contracts (extracted to avoid stack too deep)
string memory output = deployArcNetworkContracts("contracts/out/hardhat/contracts/src/", validatorRegistryProxyAddr);
string memory output = deployArcNetworkContracts("contracts/out/forge/", validatorRegistryProxyAddr);
vm.writeJson(output, outputPath);
}
}
27 changes: 27 additions & 0 deletions contracts/src/Precompiles.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2026 Circle Internet Group, Inc. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

pragma solidity ^0.8.29;

/// @title Precompiles
/// @notice Genesis-fixed addresses of Arc precompiles.
library Precompiles {
address internal constant NATIVE_COIN_AUTHORITY = 0x1800000000000000000000000000000000000000;
address internal constant NATIVE_COIN_CONTROL = 0x1800000000000000000000000000000000000001;
address internal constant SYSTEM_ACCOUNTING = 0x1800000000000000000000000000000000000002;
address internal constant CALL_FROM = 0x1800000000000000000000000000000000000003;
address internal constant PQ = 0x1800000000000000000000000000000000000004;
}
4 changes: 2 additions & 2 deletions contracts/src/batch/Multicall3From.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
pragma solidity ^0.8.29;

import {ICallFrom} from "../call-from/ICallFrom.sol";
import {Precompiles} from "../Precompiles.sol";
import {IMulticall3From} from "./IMulticall3From.sol";
import {Addresses} from "../../scripts/Addresses.sol";

/// @title Multicall3From
/// @notice Sender-preserving batch-call contract that mirrors the original
Expand All @@ -29,7 +29,7 @@ import {Addresses} from "../../scripts/Addresses.sol";
/// Reentrancy is safe: the contract holds no state that could be
/// corrupted by a reentrant call.
contract Multicall3From is IMulticall3From {
ICallFrom public constant CALL_FROM = ICallFrom(Addresses.CALL_FROM);
ICallFrom public constant CALL_FROM = ICallFrom(Precompiles.CALL_FROM);

/// @inheritdoc IMulticall3From
function aggregate(Call[] calldata calls)
Expand Down
4 changes: 2 additions & 2 deletions contracts/src/memo/Memo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
pragma solidity ^0.8.29;

import {ICallFrom} from "../call-from/ICallFrom.sol";
import {Precompiles} from "../Precompiles.sol";
import {IMemo} from "./IMemo.sol";
import {Addresses} from "../../scripts/Addresses.sol";

/**
* @title Memo
Expand All @@ -31,7 +31,7 @@ contract Memo is IMemo {
uint256 public memoIndex;

/// @notice The callFrom precompile used to forward subcalls with caller preservation.
ICallFrom public constant CALL_FROM = ICallFrom(Addresses.CALL_FROM);
ICallFrom public constant CALL_FROM = ICallFrom(Precompiles.CALL_FROM);

/// @inheritdoc IMemo
function memo(address target, bytes calldata data, bytes32 memoId, bytes calldata memoData) external {
Expand Down
4 changes: 2 additions & 2 deletions contracts/src/mocks/PrecompileCallCode.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

pragma solidity ^0.8.29;

import {Addresses} from "../../scripts/Addresses.sol";
import {Precompiles} from "../Precompiles.sol";

/**
* @title PrecompileCallCode
Expand All @@ -40,7 +40,7 @@ contract PrecompileCallCode {
bool success;

// Get the precompile address
address target = Addresses.NATIVE_COIN_AUTHORITY;
address target = Precompiles.NATIVE_COIN_AUTHORITY;

// Use assembly to perform CALLCODE and bubble up errors
assembly {
Expand Down
4 changes: 2 additions & 2 deletions contracts/src/mocks/PrecompileDelegater.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

pragma solidity ^0.8.29;

import {Addresses} from "../../scripts/Addresses.sol";
import {Precompiles} from "../Precompiles.sol";

/**
* @title PrecompileDelegater
Expand All @@ -36,7 +36,7 @@ contract PrecompileDelegater {
6660000000000000000 // 6.66 token (18 decimals)
);

(bool success, bytes memory returnData) = Addresses.NATIVE_COIN_AUTHORITY.delegatecall(mintCall);
(bool success, bytes memory returnData) = Precompiles.NATIVE_COIN_AUTHORITY.delegatecall(mintCall);

// Bubble up the error if call failed
if (!success) {
Expand Down
Loading
Loading