From 53eb7f22e36ecfeb7363625e722358d8f7f1ca52 Mon Sep 17 00:00:00 2001 From: JOY <5027251+JOY@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:23:59 +0700 Subject: [PATCH 1/4] fix: align DOS testnet deployment with current chain --- .../DeployDOSDomainPolicyTestnet.s.sol | 101 +++++++++++------- .../script/foundry/DeployDOSTestnet.s.sol | 2 +- .../foundry/Invoke-DeployDOSTestnet.ps1 | 4 +- contracts/test/unit/testnet/WrappedDOS.t.sol | 2 +- 4 files changed, 64 insertions(+), 45 deletions(-) diff --git a/contracts/script/foundry/DeployDOSDomainPolicyTestnet.s.sol b/contracts/script/foundry/DeployDOSDomainPolicyTestnet.s.sol index d2cf04fc..9b159ae7 100644 --- a/contracts/script/foundry/DeployDOSDomainPolicyTestnet.s.sol +++ b/contracts/script/foundry/DeployDOSDomainPolicyTestnet.s.sol @@ -11,7 +11,8 @@ import {PermissionedRegistry} from "~src/registry/PermissionedRegistry.sol"; /// @title Deploy DOS ID Domain Policy on DOS Testnet /// @notice Replaces the public Testnet registrar role with a policy-controlled registrar. -/// @dev Required env: PRIVATE_KEY (the canonical registry owner) and DOS_DOMAIN_VOUCHER_SIGNER. +/// @dev Required env: PRIVATE_KEY (the canonical registry owner), DOS_DOMAIN_VOUCHER_SIGNER, +/// DOS_DOMAIN_REGISTRY, DOS_DOMAIN_PRICE_ORACLE, and DOS_DOMAIN_LEGACY_REGISTRAR. /// No private voucher key is read by this deployment script. contract DeployDOSDomainPolicyTestnet is Script { //////////////////////////////////////////////////////////////////////// @@ -24,6 +25,16 @@ contract DeployDOSDomainPolicyTestnet is Script { DOSPolicyRegistrar registrar; } + /// @notice Existing DOS Name contracts and policy configuration for this deployment. + struct DeploymentConfig { + PermissionedRegistry registry; + StandardRentPriceOracle priceOracle; + address legacyRegistrar; + address voucherSigner; + uint32 minimumScore; + address defaultResolver; + } + //////////////////////////////////////////////////////////////////////// // Constants //////////////////////////////////////////////////////////////////////// @@ -34,15 +45,6 @@ contract DeployDOSDomainPolicyTestnet is Script { /// @dev Account that owns the deployed registry roles. address internal constant EXPECTED_OWNER = 0x310Bc061214ee89aF5CfB28a6ebF96c5436fa3CD; - /// @dev Existing `.dos` registry address. - address internal constant DOS_REGISTRY = 0x95366f1E44532F50c022aEefF708F424b7854173; - - /// @dev Existing `.dos` rent price oracle address. - address internal constant PRICE_ORACLE = 0x2eE958BcF29d140cdf64ad767f32E2554B0CCfa6; - - /// @dev Existing public registrar whose roles are retired by this script. - address internal constant LEGACY_DOS_REGISTRAR = 0x4E5B48aC8B221aAF8cFF070671CB6Eeea2122b5c; - /// @dev Renewable period after a name expires. uint64 internal constant GRACE_PERIOD = 28 days; @@ -56,8 +58,7 @@ contract DeployDOSDomainPolicyTestnet is Script { uint64 internal constant MIN_REGISTER_DURATION = 28 days; /// @dev Root roles moved from the legacy registrar to the policy registrar. - uint256 internal constant REGISTRAR_ROLES = - RegistryRolesLib.ROLE_REGISTRAR | RegistryRolesLib.ROLE_RENEW; + uint256 internal constant REGISTRAR_ROLES = RegistryRolesLib.ROLE_REGISTRAR | RegistryRolesLib.ROLE_RENEW; //////////////////////////////////////////////////////////////////////// // Errors @@ -83,62 +84,80 @@ contract DeployDOSDomainPolicyTestnet is Script { /// @return deployment The deployed policy and policy-controlled registrar. function run() external returns (Deployment memory deployment) { uint256 privateKey = vm.envUint("PRIVATE_KEY"); - address voucherSigner = vm.envAddress("DOS_DOMAIN_VOUCHER_SIGNER"); + DeploymentConfig memory config = deploymentConfig(); + preflight(vm.addr(privateKey), config); + + vm.startBroadcast(privateKey); + deployment.policy = deployPolicy(config); + deployment.registrar = deployRegistrar(config, deployment.policy); + config.registry.revokeRootRoles(REGISTRAR_ROLES, config.legacyRegistrar); + config.registry.grantRootRoles(REGISTRAR_ROLES, address(deployment.registrar)); + deployment.policy.setRegistrar(deployment.registrar); + vm.stopBroadcast(); + } + + /// @notice Reads and validates the runtime-specific contract configuration. + function deploymentConfig() internal view returns (DeploymentConfig memory config) { uint256 configuredMinimumScore = vm.envOr("DOS_DOMAIN_MINIMUM_SCORE", uint256(20)); - address defaultResolver = vm.envOr("DOS_DOMAIN_DEFAULT_RESOLVER", address(0)); if (configuredMinimumScore > type(uint32).max) { revert InvalidMinimumScore(configuredMinimumScore); } - address broadcaster = vm.addr(privateKey); - preflight(broadcaster); + config = DeploymentConfig({ + registry: PermissionedRegistry(vm.envAddress("DOS_DOMAIN_REGISTRY")), + priceOracle: StandardRentPriceOracle(vm.envAddress("DOS_DOMAIN_PRICE_ORACLE")), + legacyRegistrar: vm.envAddress("DOS_DOMAIN_LEGACY_REGISTRAR"), + voucherSigner: vm.envAddress("DOS_DOMAIN_VOUCHER_SIGNER"), + minimumScore: uint32(configuredMinimumScore), + defaultResolver: vm.envOr("DOS_DOMAIN_DEFAULT_RESOLVER", address(0)) + }); + } - PermissionedRegistry registry = PermissionedRegistry(DOS_REGISTRY); - vm.startBroadcast(privateKey); - deployment.policy = new DosDomainPolicy( - registry, - EXPECTED_OWNER, - voucherSigner, - uint32(configuredMinimumScore), - defaultResolver + /// @notice Deploys the score-gated policy contract. + function deployPolicy(DeploymentConfig memory config) internal returns (DosDomainPolicy) { + return new DosDomainPolicy( + config.registry, EXPECTED_OWNER, config.voucherSigner, config.minimumScore, config.defaultResolver ); - deployment.registrar = new DOSPolicyRegistrar( + } + + /// @notice Deploys the registrar that enforces the policy contract. + function deployRegistrar(DeploymentConfig memory config, DosDomainPolicy policy) + internal + returns (DOSPolicyRegistrar) + { + return new DOSPolicyRegistrar( EXPECTED_OWNER, - registry, + config.registry, EXPECTED_OWNER, - StandardRentPriceOracle(PRICE_ORACLE), + config.priceOracle, GRACE_PERIOD, MIN_COMMITMENT_AGE, MAX_COMMITMENT_AGE, MIN_REGISTER_DURATION, - address(deployment.policy) + address(policy) ); - registry.revokeRootRoles(REGISTRAR_ROLES, LEGACY_DOS_REGISTRAR); - registry.grantRootRoles(REGISTRAR_ROLES, address(deployment.registrar)); - deployment.policy.setRegistrar(deployment.registrar); - vm.stopBroadcast(); } /// @notice Validates network, broadcaster, existing contracts, and legacy registrar roles before deployment. /// @param broadcaster The address derived from the transaction private key. - function preflight(address broadcaster) public view { + function preflight(address broadcaster, DeploymentConfig memory config) internal view { if (block.chainid != EXPECTED_CHAIN_ID) { revert UnexpectedChain(block.chainid, EXPECTED_CHAIN_ID); } if (broadcaster != EXPECTED_OWNER) { revert UnexpectedOwner(broadcaster, EXPECTED_OWNER); } - if (DOS_REGISTRY.code.length == 0) { - revert MissingContractCode(DOS_REGISTRY); + if (address(config.registry).code.length == 0) { + revert MissingContractCode(address(config.registry)); } - if (PRICE_ORACLE.code.length == 0) { - revert MissingContractCode(PRICE_ORACLE); + if (address(config.priceOracle).code.length == 0) { + revert MissingContractCode(address(config.priceOracle)); } - if (LEGACY_DOS_REGISTRAR.code.length == 0) { - revert MissingContractCode(LEGACY_DOS_REGISTRAR); + if (config.legacyRegistrar.code.length == 0) { + revert MissingContractCode(config.legacyRegistrar); } - if (!PermissionedRegistry(DOS_REGISTRY).hasRootRoles(REGISTRAR_ROLES, LEGACY_DOS_REGISTRAR)) { - revert LegacyRegistrarAlreadyRetired(LEGACY_DOS_REGISTRAR); + if (!config.registry.hasRootRoles(REGISTRAR_ROLES, config.legacyRegistrar)) { + revert LegacyRegistrarAlreadyRetired(config.legacyRegistrar); } } } diff --git a/contracts/script/foundry/DeployDOSTestnet.s.sol b/contracts/script/foundry/DeployDOSTestnet.s.sol index b54fcd3d..99ad0b8b 100644 --- a/contracts/script/foundry/DeployDOSTestnet.s.sol +++ b/contracts/script/foundry/DeployDOSTestnet.s.sol @@ -23,7 +23,7 @@ import {LibLabel} from "~src/utils/LibLabel.sol"; /// @notice Deploys a standard wrapped-native WDOS token and the complete `.dos` ENSv2 stack. contract DeployDOSTestnet is DeployDOS { uint256 internal constant EXPECTED_CHAIN_ID = 3939; - address internal constant EXPECTED_DEPLOYER = 0x99999e454138f6be73E2bE82c890bc5765749999; + address internal constant EXPECTED_DEPLOYER = 0x310Bc061214ee89aF5CfB28a6ebF96c5436fa3CD; address internal constant EXPECTED_OWNER = 0x310Bc061214ee89aF5CfB28a6ebF96c5436fa3CD; uint256 internal constant MIN_DEPLOYMENT_BALANCE = 1 ether; string internal constant BENS_SMOKE_LABEL = "bens-smoke"; diff --git a/contracts/script/foundry/Invoke-DeployDOSTestnet.ps1 b/contracts/script/foundry/Invoke-DeployDOSTestnet.ps1 index 4237e481..1f53d544 100644 --- a/contracts/script/foundry/Invoke-DeployDOSTestnet.ps1 +++ b/contracts/script/foundry/Invoke-DeployDOSTestnet.ps1 @@ -9,8 +9,8 @@ $ErrorActionPreference = "Stop" $expectedRpcUrl = "https://test.doschain.com" $expectedChainId = 3939 -$expectedGenesisHash = "0x36f98b2e8b3084d57efc46622a065c2a96ed51aca86ac229bce998be6b8abd2c" -$expectedDeployer = "0x99999e454138f6be73e2be82c890bc5765749999" +$expectedGenesisHash = "0x1f6dd88694681d79a3a56313f59de56b1b6555c8e06a2ef377084f1e897feef4" +$expectedDeployer = "0x310bc061214ee89af5cfb28a6ebf96c5436fa3cd" $expectedOwner = "0x310bc061214ee89af5cfb28a6ebf96c5436fa3cd" $minimumBalanceWei = [System.Numerics.BigInteger]::Parse("1000000000000000000") diff --git a/contracts/test/unit/testnet/WrappedDOS.t.sol b/contracts/test/unit/testnet/WrappedDOS.t.sol index 69ae6b78..00db2fd0 100644 --- a/contracts/test/unit/testnet/WrappedDOS.t.sol +++ b/contracts/test/unit/testnet/WrappedDOS.t.sol @@ -23,7 +23,7 @@ contract WrappedDOSTest is Test { bytes32 internal constant ADDR_CHANGED_TOPIC = keccak256("AddrChanged(bytes32,address)"); bytes32 internal constant ADDRESS_CHANGED_TOPIC = keccak256("AddressChanged(bytes32,uint256,bytes)"); - address internal constant TESTNET_DEPLOYER = 0x99999e454138f6be73E2bE82c890bc5765749999; + address internal constant TESTNET_DEPLOYER = 0x310Bc061214ee89aF5CfB28a6ebF96c5436fa3CD; address internal constant PROTOCOL_OWNER = 0x310Bc061214ee89aF5CfB28a6ebF96c5436fa3CD; WrappedDOS internal wdos; address internal holder = makeAddr("holder"); From 05ea907cdf5e5d07c8c17a61c503222e6e3b9a27 Mon Sep 17 00:00:00 2001 From: JOY <5027251+JOY@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:35:43 +0700 Subject: [PATCH 2/4] fix: harden DOS policy deployment preflight --- .../DeployDOSDomainPolicyTestnet.s.sol | 73 +++++++----- .../Invoke-DeployDOSDomainPolicyTestnet.ps1 | 107 ++++++++++++++++++ .../DeployDOSDomainPolicyTestnet.t.sol | 68 +++++++++++ 3 files changed, 222 insertions(+), 26 deletions(-) create mode 100644 contracts/script/foundry/Invoke-DeployDOSDomainPolicyTestnet.ps1 create mode 100644 contracts/test/unit/testnet/DeployDOSDomainPolicyTestnet.t.sol diff --git a/contracts/script/foundry/DeployDOSDomainPolicyTestnet.s.sol b/contracts/script/foundry/DeployDOSDomainPolicyTestnet.s.sol index 9b159ae7..2508af70 100644 --- a/contracts/script/foundry/DeployDOSDomainPolicyTestnet.s.sol +++ b/contracts/script/foundry/DeployDOSDomainPolicyTestnet.s.sol @@ -58,7 +58,15 @@ contract DeployDOSDomainPolicyTestnet is Script { uint64 internal constant MIN_REGISTER_DURATION = 28 days; /// @dev Root roles moved from the legacy registrar to the policy registrar. - uint256 internal constant REGISTRAR_ROLES = RegistryRolesLib.ROLE_REGISTRAR | RegistryRolesLib.ROLE_RENEW; + uint256 internal constant REGISTRAR_ROLES = + RegistryRolesLib.ROLE_REGISTRAR | RegistryRolesLib.ROLE_RENEW; + + /// @dev Root admin roles required to move the registrar permissions. + uint256 internal constant REGISTRAR_ADMIN_ROLES = + RegistryRolesLib.ROLE_REGISTRAR_ADMIN | RegistryRolesLib.ROLE_RENEW_ADMIN; + + /// @dev Minimum native DOS balance required before broadcasting policy deployment. + uint256 internal constant MIN_DEPLOYMENT_BALANCE = 1 ether; //////////////////////////////////////////////////////////////////////// // Errors @@ -74,12 +82,14 @@ contract DeployDOSDomainPolicyTestnet is Script { error LegacyRegistrarAlreadyRetired(address registrar); /// @dev Error selector: `0x16319300` error InvalidMinimumScore(uint256 value); + error MissingRegistrarAdmin(address owner); + error InsufficientDeploymentBalance(uint256 actual, uint256 required); //////////////////////////////////////////////////////////////////////// // Implementation //////////////////////////////////////////////////////////////////////// - /// @notice Deploys the policy and registrar, then atomically moves registrar roles per transaction. + /// @notice Deploys the policy and registrar, then grants and revokes registrar roles in separate transactions. /// @dev Policy subsidy funding is intentionally separate: seed it only after address verification. /// @return deployment The deployed policy and policy-controlled registrar. function run() external returns (Deployment memory deployment) { @@ -90,8 +100,8 @@ contract DeployDOSDomainPolicyTestnet is Script { vm.startBroadcast(privateKey); deployment.policy = deployPolicy(config); deployment.registrar = deployRegistrar(config, deployment.policy); - config.registry.revokeRootRoles(REGISTRAR_ROLES, config.legacyRegistrar); config.registry.grantRootRoles(REGISTRAR_ROLES, address(deployment.registrar)); + config.registry.revokeRootRoles(REGISTRAR_ROLES, config.legacyRegistrar); deployment.policy.setRegistrar(deployment.registrar); vm.stopBroadcast(); } @@ -103,21 +113,25 @@ contract DeployDOSDomainPolicyTestnet is Script { revert InvalidMinimumScore(configuredMinimumScore); } - config = DeploymentConfig({ - registry: PermissionedRegistry(vm.envAddress("DOS_DOMAIN_REGISTRY")), - priceOracle: StandardRentPriceOracle(vm.envAddress("DOS_DOMAIN_PRICE_ORACLE")), - legacyRegistrar: vm.envAddress("DOS_DOMAIN_LEGACY_REGISTRAR"), - voucherSigner: vm.envAddress("DOS_DOMAIN_VOUCHER_SIGNER"), - minimumScore: uint32(configuredMinimumScore), - defaultResolver: vm.envOr("DOS_DOMAIN_DEFAULT_RESOLVER", address(0)) - }); + config = DeploymentConfig({registry: PermissionedRegistry( + vm.envAddress("DOS_DOMAIN_REGISTRY") + ), priceOracle: StandardRentPriceOracle(vm.envAddress("DOS_DOMAIN_PRICE_ORACLE")), legacyRegistrar: vm.envAddress( + "DOS_DOMAIN_LEGACY_REGISTRAR" + ), voucherSigner: vm.envAddress("DOS_DOMAIN_VOUCHER_SIGNER"), minimumScore: uint32( + configuredMinimumScore + ), defaultResolver: vm.envOr("DOS_DOMAIN_DEFAULT_RESOLVER", address(0))}); } /// @notice Deploys the score-gated policy contract. function deployPolicy(DeploymentConfig memory config) internal returns (DosDomainPolicy) { - return new DosDomainPolicy( - config.registry, EXPECTED_OWNER, config.voucherSigner, config.minimumScore, config.defaultResolver - ); + return + new DosDomainPolicy( + config.registry, + EXPECTED_OWNER, + config.voucherSigner, + config.minimumScore, + config.defaultResolver + ); } /// @notice Deploys the registrar that enforces the policy contract. @@ -125,28 +139,32 @@ contract DeployDOSDomainPolicyTestnet is Script { internal returns (DOSPolicyRegistrar) { - return new DOSPolicyRegistrar( - EXPECTED_OWNER, - config.registry, - EXPECTED_OWNER, - config.priceOracle, - GRACE_PERIOD, - MIN_COMMITMENT_AGE, - MAX_COMMITMENT_AGE, - MIN_REGISTER_DURATION, - address(policy) - ); + return + new DOSPolicyRegistrar( + EXPECTED_OWNER, + config.registry, + EXPECTED_OWNER, + config.priceOracle, + GRACE_PERIOD, + MIN_COMMITMENT_AGE, + MAX_COMMITMENT_AGE, + MIN_REGISTER_DURATION, + address(policy) + ); } /// @notice Validates network, broadcaster, existing contracts, and legacy registrar roles before deployment. /// @param broadcaster The address derived from the transaction private key. - function preflight(address broadcaster, DeploymentConfig memory config) internal view { + function preflight(address broadcaster, DeploymentConfig memory config) public view { if (block.chainid != EXPECTED_CHAIN_ID) { revert UnexpectedChain(block.chainid, EXPECTED_CHAIN_ID); } if (broadcaster != EXPECTED_OWNER) { revert UnexpectedOwner(broadcaster, EXPECTED_OWNER); } + if (broadcaster.balance < MIN_DEPLOYMENT_BALANCE) { + revert InsufficientDeploymentBalance(broadcaster.balance, MIN_DEPLOYMENT_BALANCE); + } if (address(config.registry).code.length == 0) { revert MissingContractCode(address(config.registry)); } @@ -159,5 +177,8 @@ contract DeployDOSDomainPolicyTestnet is Script { if (!config.registry.hasRootRoles(REGISTRAR_ROLES, config.legacyRegistrar)) { revert LegacyRegistrarAlreadyRetired(config.legacyRegistrar); } + if (!config.registry.hasRootRoles(REGISTRAR_ADMIN_ROLES, broadcaster)) { + revert MissingRegistrarAdmin(broadcaster); + } } } diff --git a/contracts/script/foundry/Invoke-DeployDOSDomainPolicyTestnet.ps1 b/contracts/script/foundry/Invoke-DeployDOSDomainPolicyTestnet.ps1 new file mode 100644 index 00000000..4d9e2d63 --- /dev/null +++ b/contracts/script/foundry/Invoke-DeployDOSDomainPolicyTestnet.ps1 @@ -0,0 +1,107 @@ +[CmdletBinding()] +param( + [string]$RpcUrl = "https://test.doschain.com", + [switch]$Broadcast +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = "Stop" + +$expectedRpcUrl = "https://test.doschain.com" +$expectedChainId = 3939 +$expectedGenesisHash = "0x1f6dd88694681d79a3a56313f59de56b1b6555c8e06a2ef377084f1e897feef4" +$expectedOwner = "0x310bc061214ee89af5cfb28a6ebf96c5436fa3cd" +$minimumBalanceWei = [System.Numerics.BigInteger]::Parse("1000000000000000000") +$requiredEnvironmentVariables = @( + "PRIVATE_KEY", + "DOS_DOMAIN_VOUCHER_SIGNER", + "DOS_DOMAIN_REGISTRY", + "DOS_DOMAIN_PRICE_ORACLE", + "DOS_DOMAIN_LEGACY_REGISTRAR" +) + +function Resolve-FoundryCommand { + param([Parameter(Mandatory)][string]$Name) + + $command = Get-Command $Name -ErrorAction SilentlyContinue + if ($null -ne $command) { + return $command.Source + } + + $bundled = Join-Path $env:USERPROFILE ".foundry\bin\$Name.exe" + if (Test-Path -LiteralPath $bundled) { + return $bundled + } + + throw "$Name was not found in PATH or the Foundry installation directory" +} + +if ($RpcUrl.TrimEnd("/") -ne $expectedRpcUrl) { + throw "RPC URL must be $expectedRpcUrl" +} + +foreach ($name in $requiredEnvironmentVariables) { + $environmentItem = Get-Item -Path "Env:$name" -ErrorAction SilentlyContinue + if ($null -eq $environmentItem -or [string]::IsNullOrWhiteSpace($environmentItem.Value)) { + throw "$name is required" + } +} + +$privateKey = $env:PRIVATE_KEY.Trim() +if ($privateKey -match "^[0-9a-fA-F]{64}$") { + $privateKey = "0x$privateKey" +} +if ($privateKey -notmatch "^0x[0-9a-fA-F]{64}$") { + throw "PRIVATE_KEY must be a 32-byte hexadecimal value" +} +$env:PRIVATE_KEY = $privateKey + +$cast = Resolve-FoundryCommand -Name "cast" +$forge = Resolve-FoundryCommand -Name "forge" + +$chainId = [int]((& $cast chain-id --rpc-url $RpcUrl).Trim()) +if ($LASTEXITCODE -ne 0 -or $chainId -ne $expectedChainId) { + throw "RPC chain ID does not match DOS Testnet" +} + +$genesis = (& $cast block 0 --rpc-url $RpcUrl --json | ConvertFrom-Json).hash.ToLowerInvariant() +if ($LASTEXITCODE -ne 0 -or $genesis -ne $expectedGenesisHash) { + throw "RPC genesis hash does not match DOS Testnet" +} + +$balanceOutput = & $cast balance $expectedOwner --rpc-url $RpcUrl +if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($balanceOutput)) { + throw "Unable to read the canonical owner balance" +} +$balance = [System.Numerics.BigInteger]::Parse($balanceOutput.Trim()) +if ($balance -lt $minimumBalanceWei) { + throw "Canonical owner balance is below the 1 DOS deployment floor" +} + +$contractsRoot = Resolve-Path (Join-Path $PSScriptRoot "..\..") +Push-Location $contractsRoot +try { + & $forge script "script/foundry/DeployDOSDomainPolicyTestnet.s.sol:DeployDOSDomainPolicyTestnet" ` + --rpc-url $RpcUrl ` + --slow + if ($LASTEXITCODE -ne 0) { + throw "Foundry simulation failed" + } + + if (-not $Broadcast) { + Write-Output "DOS_TESTNET_DOMAIN_POLICY_SIMULATION_OK" + return + } + + & $forge script "script/foundry/DeployDOSDomainPolicyTestnet.s.sol:DeployDOSDomainPolicyTestnet" ` + --rpc-url $RpcUrl ` + --broadcast ` + --slow + if ($LASTEXITCODE -ne 0) { + throw "Foundry broadcast failed" + } + Write-Output "DOS_TESTNET_DOMAIN_POLICY_BROADCAST_OK" +} +finally { + Pop-Location +} diff --git a/contracts/test/unit/testnet/DeployDOSDomainPolicyTestnet.t.sol b/contracts/test/unit/testnet/DeployDOSDomainPolicyTestnet.t.sol new file mode 100644 index 00000000..06cf60b5 --- /dev/null +++ b/contracts/test/unit/testnet/DeployDOSDomainPolicyTestnet.t.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.20; + +import { + DeployDOSDomainPolicyTestnet +} from "../../../script/foundry/DeployDOSDomainPolicyTestnet.s.sol"; + +import {RegistryRolesLib} from "~src/registry/libraries/RegistryRolesLib.sol"; +import {StandardRentPriceOracleFixture} from "~test/fixtures/StandardRentPriceOracleFixture.sol"; +import {V2Fixture} from "~test/fixtures/V2Fixture.sol"; + +contract DeployDOSDomainPolicyTestnetTest is V2Fixture, StandardRentPriceOracleFixture { + address internal constant EXPECTED_OWNER = 0x310Bc061214ee89aF5CfB28a6ebF96c5436fa3CD; + uint256 internal constant REGISTRAR_ROLES = + RegistryRolesLib.ROLE_REGISTRAR | RegistryRolesLib.ROLE_RENEW; + uint256 internal constant REGISTRAR_ADMIN_ROLES = + RegistryRolesLib.ROLE_REGISTRAR_ADMIN | RegistryRolesLib.ROLE_RENEW_ADMIN; + + DeployDOSDomainPolicyTestnet internal deployer; + + function setUp() external { + deployV2Fixture(); + deployStandardRentPriceOracleFixture(); + deployer = new DeployDOSDomainPolicyTestnet(); + + vm.chainId(3939); + vm.deal(EXPECTED_OWNER, 1 ether); + ethRegistry.grantRootRoles(RegistryRolesLib.ROLE_RENEW, address(this)); + ethRegistry.grantRootRoles(REGISTRAR_ADMIN_ROLES, EXPECTED_OWNER); + } + + function test_preflightAcceptsCanonicalOwnerWithRegistrarAdminRoles() external { + deployer.preflight(EXPECTED_OWNER, _config()); + } + + function test_preflightRejectsOwnerWithoutRegistrarAdminRoles() external { + ethRegistry.revokeRootRoles(REGISTRAR_ADMIN_ROLES, EXPECTED_OWNER); + + vm.expectRevert( + abi.encodeWithSelector( + DeployDOSDomainPolicyTestnet.MissingRegistrarAdmin.selector, + EXPECTED_OWNER + ) + ); + deployer.preflight(EXPECTED_OWNER, _config()); + } + + function test_preflightRejectsLegacyRegistrarWithoutActiveRoles() external { + ethRegistry.revokeRootRoles(REGISTRAR_ROLES, address(this)); + + vm.expectRevert( + abi.encodeWithSelector( + DeployDOSDomainPolicyTestnet.LegacyRegistrarAlreadyRetired.selector, + address(this) + ) + ); + deployer.preflight(EXPECTED_OWNER, _config()); + } + + function _config() + internal + returns (DeployDOSDomainPolicyTestnet.DeploymentConfig memory config) + { + config = DeployDOSDomainPolicyTestnet.DeploymentConfig({registry: ethRegistry, priceOracle: rentPriceOracle, legacyRegistrar: address( + this + ), voucherSigner: makeAddr("voucherSigner"), minimumScore: 20, defaultResolver: address(0)}); + } +} From b62f58168f6f9e855e5b2ac4b56b2cf52f1b13cd Mon Sep 17 00:00:00 2001 From: JOY <5027251+JOY@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:46:43 +0700 Subject: [PATCH 3/4] test: avoid flaky Devnet sync timestamp assertion --- contracts/test/e2e/devnet.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/test/e2e/devnet.test.ts b/contracts/test/e2e/devnet.test.ts index ade73b4c..b1837e7a 100644 --- a/contracts/test/e2e/devnet.test.ts +++ b/contracts/test/e2e/devnet.test.ts @@ -12,7 +12,7 @@ describe("Devnet", () => { const t = await env.sync(); const block1 = await env.client.getBlock(); expect(block1.timestamp).toBeGreaterThanOrEqual(block0.timestamp); - expectVar({ t }).toStrictEqual(block1.timestamp); + expectVar({ t }).toBeLessThanOrEqual(block1.timestamp); }); it("warp", async () => { From e69dafeda990389a1845ffe9a03ac8af22bcf1d4 Mon Sep 17 00:00:00 2001 From: JOY <5027251+JOY@users.noreply.github.com> Date: Sat, 22 Aug 2026 13:48:10 +0700 Subject: [PATCH 4/4] test: preserve Devnet sync timestamp bounds --- contracts/test/e2e/devnet.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/test/e2e/devnet.test.ts b/contracts/test/e2e/devnet.test.ts index b1837e7a..9b1ce00e 100644 --- a/contracts/test/e2e/devnet.test.ts +++ b/contracts/test/e2e/devnet.test.ts @@ -12,6 +12,7 @@ describe("Devnet", () => { const t = await env.sync(); const block1 = await env.client.getBlock(); expect(block1.timestamp).toBeGreaterThanOrEqual(block0.timestamp); + expectVar({ t }).toBeGreaterThanOrEqual(block0.timestamp); expectVar({ t }).toBeLessThanOrEqual(block1.timestamp); });