Spaces:
Running
Running
| """Deploy the PolyglotAlpha v2 contract suite to Arc testnet. | |
| Deploys (in dependency order): | |
| 1. ReputationRegistry (no constructor args) | |
| 2. TranslationAuction (usdc, reputationRegistry) | |
| 3. BuilderFeeRouter (usdc, reputationRegistry) | |
| 4. JudgePanel (usdc) -- NEW (README §5.6 / §5.22) | |
| QuestionRegistry is **not** redeployed by default — it has no incoming wiring | |
| to the contracts in this MR (per README §5.7 it is its own provenance log, | |
| written to from the orchestrator's settlement step). To redeploy it pass | |
| ``--include-question-registry``. | |
| After deployment the script wires up authorization: | |
| reputation.setAuthorized(translationAuction, true) | |
| reputation.setAuthorized(builderFeeRouter, true) | |
| reputation.setAuthorized(judgePanel, true) -- so slashReputation | |
| can be invoked | |
| And it prints copy-pasteable ``.env`` lines. | |
| Usage:: | |
| .venv/bin/python scripts/deploy_all_contracts.py | |
| Environment:: | |
| ARC_TESTNET_RPC (default https://rpc.testnet.arc.network) | |
| ARC_CHAIN_ID (default 5042002) | |
| HACKATHON_WALLET_PRIVATE_KEY (required) | |
| ARC_TESTNET_USDC_ADDRESS (required) | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import os | |
| import sys | |
| import time | |
| from pathlib import Path | |
| from eth_account import Account | |
| from web3 import Web3 | |
| REPO_ROOT = Path(__file__).resolve().parents[1] | |
| FOUNDRY_OUT = REPO_ROOT / "contracts" / "out" | |
| def load_artifact(name: str) -> dict: | |
| """Load a Foundry artifact (ABI + bytecode) from contracts/out/.""" | |
| path = FOUNDRY_OUT / f"{name}.sol" / f"{name}.json" | |
| with path.open("r", encoding="utf-8") as fh: | |
| art = json.load(fh) | |
| return { | |
| "abi": art["abi"], | |
| "bytecode": art["bytecode"]["object"], | |
| } | |
| def wait_receipt(w3: Web3, tx_hash: bytes, timeout: int = 180) -> dict: | |
| receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=timeout) | |
| if receipt.status != 1: | |
| raise RuntimeError(f"tx failed: {tx_hash.hex()}") | |
| return receipt | |
| def send_signed(w3: Web3, txn: dict, account) -> dict: | |
| signed = w3.eth.account.sign_transaction(txn, account.key) | |
| raw = getattr(signed, "raw_transaction", None) or signed.rawTransaction | |
| tx_hash = w3.eth.send_raw_transaction(raw) | |
| return wait_receipt(w3, tx_hash) | |
| def deploy_contract( | |
| w3: Web3, | |
| account, | |
| chain_id: int, | |
| name: str, | |
| constructor_args: tuple = (), | |
| ) -> tuple[str, int]: | |
| """Deploy a single contract; returns (address, gas_used).""" | |
| art = load_artifact(name) | |
| contract = w3.eth.contract(abi=art["abi"], bytecode=art["bytecode"]) | |
| nonce = w3.eth.get_transaction_count(account.address) | |
| txn = contract.constructor(*constructor_args).build_transaction( | |
| { | |
| "from": account.address, | |
| "nonce": nonce, | |
| "chainId": chain_id, | |
| "gasPrice": w3.eth.gas_price, | |
| "gas": 4_500_000, | |
| } | |
| ) | |
| print(f" -> deploying {name}({', '.join(map(str, constructor_args))}) ...") | |
| receipt = send_signed(w3, txn, account) | |
| addr = receipt.contractAddress | |
| print(f" deployed at {addr} (gas {receipt.gasUsed})") | |
| return addr, receipt.gasUsed | |
| def authorize( | |
| w3: Web3, | |
| account, | |
| chain_id: int, | |
| rep_address: str, | |
| rep_abi: list, | |
| who: str, | |
| label: str, | |
| ) -> int: | |
| """Call ReputationRegistry.setAuthorized(who, true); returns gas used.""" | |
| contract = w3.eth.contract(address=Web3.to_checksum_address(rep_address), abi=rep_abi) | |
| nonce = w3.eth.get_transaction_count(account.address) | |
| txn = contract.functions.setAuthorized( | |
| Web3.to_checksum_address(who), True | |
| ).build_transaction( | |
| { | |
| "from": account.address, | |
| "nonce": nonce, | |
| "chainId": chain_id, | |
| "gasPrice": w3.eth.gas_price, | |
| "gas": 120_000, | |
| } | |
| ) | |
| print(f" -> setAuthorized({label} = {who}, true) ...") | |
| receipt = send_signed(w3, txn, account) | |
| print(f" authorized (gas {receipt.gasUsed})") | |
| return receipt.gasUsed | |
| def main() -> int: | |
| parser = argparse.ArgumentParser(description="Deploy PolyglotAlpha v2 contracts") | |
| parser.add_argument( | |
| "--include-question-registry", | |
| action="store_true", | |
| help="Also redeploy QuestionRegistry (not normally required for §5.6 corrections).", | |
| ) | |
| args = parser.parse_args() | |
| pk = os.environ.get("HACKATHON_WALLET_PRIVATE_KEY") | |
| if not pk: | |
| print("ERROR: HACKATHON_WALLET_PRIVATE_KEY is required", file=sys.stderr) | |
| return 2 | |
| usdc_address = os.environ.get( | |
| "ARC_TESTNET_USDC_ADDRESS", | |
| "0x477fC4C3DcC87C3Ceb13adc931F6bBeDAcCa391D", | |
| ) | |
| rpc_url = os.environ.get("ARC_TESTNET_RPC", "https://rpc.testnet.arc.network") | |
| chain_id = int(os.environ.get("ARC_CHAIN_ID", "5042002")) | |
| w3 = Web3(Web3.HTTPProvider(rpc_url)) | |
| if not w3.is_connected(): | |
| print(f"ERROR: cannot reach Arc RPC at {rpc_url}", file=sys.stderr) | |
| return 3 | |
| account = Account.from_key(pk) | |
| print(f"deployer: {account.address}") | |
| print( | |
| f"balance: {w3.from_wei(w3.eth.get_balance(account.address), 'ether')} ETH-equivalent" | |
| ) | |
| print(f"chain id: {chain_id}") | |
| print(f"usdc: {usdc_address}") | |
| print() | |
| deployed: dict[str, str] = {} | |
| gas_used: dict[str, int] = {} | |
| # 1. ReputationRegistry | |
| rep_addr, gas = deploy_contract( | |
| w3, account, chain_id, "ReputationRegistry" | |
| ) | |
| deployed["ReputationRegistry"] = rep_addr | |
| gas_used["ReputationRegistry"] = gas | |
| # 2. TranslationAuction | |
| auct_addr, gas = deploy_contract( | |
| w3, | |
| account, | |
| chain_id, | |
| "TranslationAuction", | |
| (Web3.to_checksum_address(usdc_address), Web3.to_checksum_address(rep_addr)), | |
| ) | |
| deployed["TranslationAuction"] = auct_addr | |
| gas_used["TranslationAuction"] = gas | |
| # 3. BuilderFeeRouter | |
| router_addr, gas = deploy_contract( | |
| w3, | |
| account, | |
| chain_id, | |
| "BuilderFeeRouter", | |
| (Web3.to_checksum_address(usdc_address), Web3.to_checksum_address(rep_addr)), | |
| ) | |
| deployed["BuilderFeeRouter"] = router_addr | |
| gas_used["BuilderFeeRouter"] = gas | |
| # 4. JudgePanel (NEW per §5.6 / §5.22) | |
| panel_addr, gas = deploy_contract( | |
| w3, account, chain_id, "JudgePanel", | |
| (Web3.to_checksum_address(usdc_address),), | |
| ) | |
| deployed["JudgePanel"] = panel_addr | |
| gas_used["JudgePanel"] = gas | |
| if args.include_question_registry: | |
| qr_addr, gas = deploy_contract(w3, account, chain_id, "QuestionRegistry") | |
| deployed["QuestionRegistry"] = qr_addr | |
| gas_used["QuestionRegistry"] = gas | |
| # 5. Wire up authorization on ReputationRegistry so the three downstream | |
| # contracts can push updates / slash. The deployer is already authorized. | |
| rep_art = load_artifact("ReputationRegistry") | |
| for label, addr in ( | |
| ("TranslationAuction", auct_addr), | |
| ("BuilderFeeRouter", router_addr), | |
| ("JudgePanel", panel_addr), | |
| ): | |
| gas = authorize( | |
| w3, account, chain_id, rep_addr, rep_art["abi"], addr, label | |
| ) | |
| gas_used[f"auth({label})"] = gas | |
| final_balance = w3.from_wei(w3.eth.get_balance(account.address), "ether") | |
| print() | |
| print("=== Deployment summary ===") | |
| for name, addr in deployed.items(): | |
| print(f" {name}: {addr} (gas {gas_used[name]})") | |
| print() | |
| print("=== Gas used (authorization) ===") | |
| for label, g in gas_used.items(): | |
| if label.startswith("auth("): | |
| print(f" {label}: {g}") | |
| print() | |
| total_gas = sum(gas_used.values()) | |
| print(f"total gas: {total_gas}") | |
| print(f"final balance: {final_balance} ETH-equivalent") | |
| print() | |
| print("=== Suggested .env updates ===") | |
| print(f"REPUTATION_REGISTRY_ADDRESS={rep_addr}") | |
| print(f"TRANSLATION_AUCTION_ADDRESS={auct_addr}") | |
| print(f"BUILDER_FEE_ROUTER_ADDRESS={router_addr}") | |
| print(f"JUDGE_PANEL_ADDRESS={panel_addr}") | |
| if "QuestionRegistry" in deployed: | |
| print(f"QUESTION_REGISTRY_ADDRESS={deployed['QuestionRegistry']}") | |
| print() | |
| # Dump a JSON file alongside for programmatic consumers. | |
| out_path = REPO_ROOT / "outputs" / "deployment_v2.json" | |
| out_path.parent.mkdir(parents=True, exist_ok=True) | |
| out_path.write_text( | |
| json.dumps( | |
| { | |
| "chain_id": chain_id, | |
| "deployer": account.address, | |
| "usdc": usdc_address, | |
| "deployed_at": int(time.time()), | |
| "addresses": deployed, | |
| "gas_used": gas_used, | |
| }, | |
| indent=2, | |
| ) | |
| ) | |
| print(f"wrote {out_path}") | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |