├── .env.example ├── .github └── workflows │ ├── forge-test.yml │ └── foundry-gas-diff.yml ├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── assets ├── clusters-animation.mov └── hub-and-relay-and-spoke.jpg ├── foundry.toml ├── remappings.txt ├── script ├── BatchInteraction.s.sol ├── Clusters.s.sol ├── Deploy.s.sol ├── DeployBeta.s.sol ├── SetConfig.s.sol ├── SetDVNConfig.s.sol ├── Testnet.s.sol └── Testpad.s.sol ├── src ├── BasicUUPSImpl.sol ├── ClustersHub.sol ├── Endpoint.sol ├── EnumerableSetLib.sol ├── NameManagerHub.sol ├── PricingFlat.sol ├── PricingHarberger.sol ├── beta │ ├── ClustersHubBeta.sol │ └── ClustersInitiatorBeta.sol └── interfaces │ ├── IClustersHub.sol │ ├── IEndpoint.sol │ ├── IPricing.sol │ └── IUUPS.sol └── test ├── Base.t.sol ├── ClustersBetaCrossChain.t.sol ├── ClustersBetaSingleChain.t.sol ├── EnumerableSetLib.t.sol ├── GasBenchmark.t.sol ├── harness └── PricingHarbergerHarness.sol ├── mocks └── FickleReceiver.sol ├── unit ├── local │ ├── concrete │ │ ├── Clusters_acceptBid.t.sol │ │ ├── Clusters_add.t.sol │ │ ├── Clusters_bidName.t.sol │ │ ├── Clusters_buyName.t.sol │ │ ├── Clusters_fundName.t.sol │ │ ├── Clusters_multicall.t.sol │ │ ├── Clusters_pokeName.t.sol │ │ ├── Clusters_reduceBid.t.sol │ │ ├── Clusters_refundBid.t.sol │ │ ├── Clusters_remove.t.sol │ │ ├── Clusters_setDefaultClusterName.t.sol │ │ ├── Clusters_setWalletName.t.sol │ │ ├── Clusters_transferName.t.sol │ │ ├── Clusters_verify.t.sol │ │ ├── Endpoint_ECDSA.t.sol │ │ ├── Endpoint_fulfillOrder.t.sol │ │ ├── Endpoint_multicall.t.sol │ │ ├── Endpoint_setClustersAddr.t.sol │ │ ├── Endpoint_setSignerAddr.t.sol │ │ ├── Endpoint_upgradeToAndCall.t.sol │ │ ├── PricingHarberger.t.sol │ │ └── Pricing_upgradeToAndCall.t.sol │ └── shared │ │ ├── SharedPricingFlat.t.sol │ │ └── SharedPricingHarberger.t.sol └── remote │ ├── concrete │ ├── inbound │ │ ├── Endpoint_add.t.sol │ │ ├── Endpoint_bidName.t.sol │ │ ├── Endpoint_buyName.t.sol │ │ ├── Endpoint_fundName.t.sol │ │ ├── Endpoint_gasAirdrop.t.sol │ │ ├── Endpoint_pokeName.t.sol │ │ ├── Endpoint_refund.t.sol │ │ ├── Endpoint_remove.t.sol │ │ ├── Endpoint_setDefaultClusterName.t.sol │ │ ├── Endpoint_setWalletName.t.sol │ │ ├── Endpoint_transferName.t.sol │ │ └── Endpoint_verify.t.sol │ └── outbound │ │ └── Endpoint_gasAirdrop.t.sol │ └── shared │ └── SharedInboundHarbergerTest.t.sol └── utils ├── Constants.sol ├── Types.sol └── Utils.sol /.env.example: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY= 2 | ETHERSCAN_API_KEY="" 3 | SEPOLIA_RPC_URL="" 4 | HOLESKY_RPC_URL="" -------------------------------------------------------------------------------- /.github/workflows/forge-test.yml: -------------------------------------------------------------------------------- 1 | name: Forge Tests 2 | 3 | env: 4 | FOUNDRY_PROFILE: "ci" 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | forge-tests: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v3 13 | 14 | - name: Install Foundry 15 | uses: onbjerg/foundry-toolchain@v1 16 | with: 17 | version: nightly 18 | 19 | - name: Install dependencies 20 | run: forge install 21 | # NOTE: must run `forge update` explicitly, repo uses cached deps instead of pulling fresh 22 | 23 | - name: Yarn Install and Build 24 | run: | 25 | cd lib/LayerZero-v2 26 | yarn 27 | yarn build 28 | 29 | - name: Install pnpm 30 | uses: pnpm/action-setup@v3 31 | with: 32 | version: 8 33 | 34 | - name: pnpm Install and Build 35 | run: | 36 | cd lib/devtools 37 | pnpm install --reporter=append-only 38 | 39 | - name: Check formatting 40 | run: forge fmt --check 41 | 42 | - name: Run forge tests 43 | # env: 44 | # ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }} 45 | # run: forge test --fork-url "https://eth-mainnet.alchemyapi.io/v2/$ALCHEMY_API_KEY" 46 | run: forge test --ffi -vvv -------------------------------------------------------------------------------- /.github/workflows/foundry-gas-diff.yml: -------------------------------------------------------------------------------- 1 | name: Report gas diff 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | # Optionally configure to run only for changes in specific files. For example: 9 | # paths: 10 | # - src/** 11 | # - test/** 12 | # - foundry.toml 13 | # - remappings.txt 14 | # - .github/workflows/foundry-gas-diff.yml 15 | 16 | jobs: 17 | compare_gas_reports: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: recursive 23 | 24 | - name: Install Foundry 25 | uses: onbjerg/foundry-toolchain@v1 26 | with: 27 | version: nightly 28 | 29 | - name: Install dependencies 30 | run: forge install 31 | 32 | - name: Yarn Install and Build 33 | run: | 34 | cd lib/LayerZero-v2 35 | yarn 36 | yarn build 37 | 38 | - name: Install pnpm 39 | uses: pnpm/action-setup@v3 40 | with: 41 | version: 8 42 | 43 | - name: pnpm Install and Build 44 | run: | 45 | cd lib/devtools 46 | pnpm install --reporter=append-only 47 | 48 | # Add any step generating a gas report to a temporary file named gasreport.ansi. For example: 49 | - name: Run tests 50 | run: forge test --match-contract GasBenchmarkTest --gas-report > gasreport.ansi # <- this file name should be unique in your repository! 51 | env: 52 | # make fuzzing semi-deterministic to avoid noisy gas cost estimation 53 | # due to non-deterministic fuzzing (but still use pseudo-random fuzzing seeds) 54 | FOUNDRY_FUZZ_SEED: 0x${{ github.event.pull_request.base.sha || github.sha }} 55 | 56 | - name: Compare gas reports 57 | uses: Rubilmax/foundry-gas-diff@v3.18 58 | with: 59 | summaryQuantile: 0.0 # display all gas diffs 60 | sortCriteria: avg,max # sort diff rows by criteria 61 | sortOrders: desc,asc # and directions 62 | ignore: test-foundry/**/* # filter out gas reports from specific paths (test/ is included by default) 63 | id: gas_diff 64 | 65 | - name: Add gas diff to sticky comment 66 | if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target' 67 | uses: marocchino/sticky-pull-request-comment@v2 68 | with: 69 | # delete the comment in case changes no longer impact gas costs 70 | delete: ${{ !steps.gas_diff.outputs.markdown }} 71 | message: ${{ steps.gas_diff.outputs.markdown }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | broadcast/ 7 | 8 | # Docs 9 | docs/ 10 | 11 | # Dotenv file 12 | .env 13 | 14 | .DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/forge-std"] 2 | path = lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | [submodule "lib/openzeppelin-contracts"] 5 | path = lib/openzeppelin-contracts 6 | url = https://github.com/OpenZeppelin/openzeppelin-contracts 7 | [submodule "lib/solady"] 8 | path = lib/solady 9 | url = https://github.com/Vectorized/solady 10 | [submodule "lib/LayerZero-v2"] 11 | path = lib/LayerZero-v2 12 | url = https://github.com/clustersxyz/LayerZero-v2 13 | [submodule "lib/openzeppelin-contracts-upgradeable"] 14 | path = lib/openzeppelin-contracts-upgradeable 15 | url = https://github.com/openzeppelin/openzeppelin-contracts-upgradeable 16 | [submodule "lib/devtools"] 17 | path = lib/devtools 18 | url = https://github.com/0xfoobar/devtools 19 | branch = patch-1 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | script-local: 2 | forge script script/Clusters.s.sol 3 | 4 | sim-deploy-testnet: 5 | forge script -vvvvv script/Testnet.s.sol --sig "run()" 6 | 7 | deploy-testnet: 8 | forge script -vvv script/Testnet.s.sol --sig "run()" --broadcast 9 | 10 | call-beta-hub: 11 | # For live deployment, add --broadcast --verify --delay 30 --etherscan-api-key ${ETHERSCAN_API_KEY} 12 | # forge script -vvvvv script/DeployBeta.s.sol --sig "configureHub()" --fork-url ${ETHEREUM_RPC_URL} --private-key ${PK} 13 | # forge script -vvv script/DeployBeta.s.sol --sig "upgradeInitiator()" --fork-url ${PLUME_RPC_URL} --private-key ${PK} 14 | 15 | call-batch-refund: 16 | forge script -vvv script/BatchInteraction.s.sol --sig "run()" 17 | 18 | call-beta-initiator: 19 | forge script -vvv script/DeployBeta.s.sol --sig "doInitiate()" --fork-url ${PLUME_RPC_URL} --private-key ${PK} -vvv --broadcast 20 | # forge script -vvv script/SetDVNConfig.s.sol --sig "setDestReceiveConfig()" -vvv --broadcast 21 | 22 | verify: 23 | forge verify-contract 0xa8a8157F4ed368F9d15468670253aC00c5661Ba9 src/beta/ClustersInitiatorBeta.sol:ClustersInitiatorBeta --chain 167000 --etherscan-api-key ${ETHERSCAN_API_KEY} --retries 5 --delay 30 --watch 24 | 25 | cast-deploy-vanityimpl-placeholder: 26 | cast send --rpc-url ${PLUME_RPC_URL} --private-key ${PK} 0x0000000000ffe8b47b3e2130213b802212439497 0x64e03087000000000000000000000000000000000000000097e5b90d2f1f6025db407f4d000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002db60a06040523060805234801561001457600080fd5b506080516102a561003660003960008181606c015261015201526102a56000f3fe6080604052600436106100295760003560e01c80634f1ef2861461002e57806352d1902d14610043575b600080fd5b61004161003c3660046101fa565b61006a565b005b34801561004f57600080fd5b5061005861014e565b60405190815260200160405180910390f35b7f00000000000000000000000000000000000000000000000000000000000000003081036100a057639f03a0266000526004601cfd5b6100a9846101ad565b8360601b60601c93506352d1902d6001527f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80602060016004601d895afa51146100fb576355299b496001526004601dfd5b847fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b600038a2849055811561014857604051828482376000388483885af4610146573d6000823e3d81fd5b505b50505050565b60007f000000000000000000000000000000000000000000000000000000000000000030811461018657639f03a0266000526004601cfd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc91505090565b70de1e80ea5a234fb5488fee2584251bc7e833146101f7576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b60008060006040848603121561020f57600080fd5b833573ffffffffffffffffffffffffffffffffffffffff8116811461023357600080fd5b9250602084013567ffffffffffffffff8082111561025057600080fd5b818601915086601f83011261026457600080fd5b81358181111561027357600080fd5b87602082850101111561028557600080fd5b602083019450809350505050925092509256fea164736f6c6343000817000a0000000000 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Clusters 2 | 3 | 4 | 5 | **Clusters is the first natively crosschain name service.** 6 | 7 | Clusters is: 8 | 9 | - **Crosschain**: Register a name on any of 10+ chains, then global state is relayed to Ethereum and the rest. 10 | - **Wallet Bundles**: Join all your wallet addresses under a single name. EVM, Solana, Bitcoin, etc. 11 | - **No More Squatting**: Our bid-based dynamic pricing means squatters pay a heavy price for stealing names. 12 | 13 | ![](assets/clusters-animation.mov) 14 | 15 | ## Website 16 | 17 | https://clusters.xyz 18 | 19 | ## Technical Architecture 20 | 21 | ### Crosschain 22 | 23 | We apply a hub-and-relay-and-spoke model built on LayerZero. Suppose that the protocol lives on 5 EVM chains - Ethereum, Polygon, Arbitrum, Optimism, and BSC. A user can initiate a cluster creation (or other action) from BSC. The intent data is relayed from BSC to Ethereum, where the tx is ordered and finalized. Then Ethereum sends along the finalized state diff to the relay network (say Polygon) which will further send to the rest of the spokes: Arbitrum and Optimism. This allows clean O(1) tx cost on expensive Ethereum while scaling to a dozen+ spokes in due time. ![Blue is Ethereum, green is the relay chain, yellow is all other chains](assets/hub-and-relay-and-spoke.jpg) 24 | 25 | ### Wallet Bundles 26 | 27 | Each cluster is uniquely denoted by its `uint256 clusterId`, an incrementing integer. A cluster contains one or more `bytes32 address`, which can be an EVM address or other address formats. A user creates a cluster and can add other addresses in the same multicall transaction. These addresses are `unverified` cluster members until they accept the invitation and become `verified` cluster members. 28 | 29 | Then a wide variety of apps (gaming leaderboards, points systems, connect wallet modals) can read this onchain cluster membership data and aggregate user profiles onchain and offchain. 30 | 31 | ### No More Squatting 32 | 33 | The failure case of many nameservices is becoming overrun by squatters. Excessive speculation can actually reduce net economic welfare. Inspired by [vitalik's blog post](https://vitalik.ca/general/2022/09/09/ens.html), we introduce demand-based capped pricing. Names have a flat annual fee (say 0.01 eth) that increases up to a cap if and only if others make verifiable onchain bids on that name. This means the median user with a specific name will pay the minimum, and squatters will pay higher recurring fees making squatting economically infeasible. 34 | -------------------------------------------------------------------------------- /assets/clusters-animation.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clustersxyz/clusters/569eed645181e7a661b1488063124ffd7eda5039/assets/clusters-animation.mov -------------------------------------------------------------------------------- /assets/hub-and-relay-and-spoke.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clustersxyz/clusters/569eed645181e7a661b1488063124ffd7eda5039/assets/hub-and-relay-and-spoke.jpg -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | auto_detect_remappings = false 3 | bytecode_hash = "none" # Get reproducible bytecode across machines by removing metadata hash from runtime bytecode 4 | solc_version = "0.8.26" 5 | evm_version = "cancun" 6 | fuzz = { runs = 256 } 7 | gas_reports = ["*"] 8 | libs = ["lib"] 9 | optimizer = true 10 | optimizer_runs = 9_999_999 # Etherscan max is 100 million 11 | out = "out" 12 | script = "script" 13 | src = "src" 14 | test = "test" 15 | via_ir = false # Disable ir optimizer, minimal gas savings not worth the potential for bugs 16 | 17 | [profile.ci] 18 | fuzz = { runs = 10_000 } 19 | verbosity = 4 20 | 21 | [fmt] 22 | line_length = 120 23 | wrap_comments = true 24 | 25 | [rpc_endpoints] 26 | sepolia = "${SEPOLIA_RPC_URL}" 27 | holesky = "${HOLESKY_RPC_URL}" 28 | 29 | [etherscan] 30 | sepolia = { key = "${ETHERSCAN_API_KEY}" } 31 | holesky = { key = "${ETHERSCAN_API_KEY}" } -------------------------------------------------------------------------------- /remappings.txt: -------------------------------------------------------------------------------- 1 | openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ 2 | ds-test/=lib/forge-std/lib/ds-test/src/ 3 | erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ 4 | forge-std/=lib/forge-std/src/ 5 | openzeppelin-contracts/=lib/openzeppelin-contracts/contracts/ 6 | solmate/=lib/solmate/src/ 7 | solady/=lib/solady/src/ 8 | clusters/=src/ 9 | solidity-bytes-utils/=lib/LayerZero-v2/oapp/node_modules/solidity-bytes-utils/ 10 | @openzeppelin/contracts/=lib/LayerZero-v2/oapp/node_modules/@openzeppelin/contracts/ 11 | @openzeppelin/contracts-upgradeable/=lib/LayerZero-v2/oapp/node_modules/@openzeppelin/contracts-upgradeable/ 12 | @layerzerolabs/lz-evm-protocol-v2/=lib/LayerZero-v2/oapp/node_modules/@layerzerolabs/lz-evm-protocol-v2/ 13 | @layerzerolabs/lz-evm-messagelib-v2/=lib/LayerZero-v2/oapp/node_modules/@layerzerolabs/lz-evm-messagelib-v2/ 14 | @layerzerolabs/lz-evm-oapp-v2/=lib/devtools/packages/test-devtools-evm-hardhat/node_modules/@layerzerolabs/lz-evm-oapp-v2/ 15 | layerzero-oapp/=lib/LayerZero-v2/oapp/ 16 | devtools/=lib/devtools/packages/test-devtools-evm-hardhat/contracts/ 17 | openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ 18 | -------------------------------------------------------------------------------- /script/BatchInteraction.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console2} from "forge-std/Script.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | import {Endpoint} from "../src/Endpoint.sol"; 8 | import {ClustersHubBeta} from "../src/beta/ClustersHubBeta.sol"; 9 | 10 | interface GasliteDrop { 11 | function airdropETH(address[] calldata _addresses, uint256[] calldata _amounts) external payable; 12 | } 13 | 14 | contract BatchInteractionScript is Script { 15 | using OptionsBuilder for bytes; 16 | 17 | uint256 internal sepoliaFork; 18 | Endpoint internal endpoint; 19 | ClustersHubBeta internal proxy; 20 | GasliteDrop internal drop; 21 | 22 | function addressToBytes32(address addr) internal pure returns (bytes32) { 23 | return bytes32(uint256(uint160(addr))); 24 | } 25 | 26 | function setUp() public { 27 | uint256 mainnetFork = vm.createFork("https://eth.llamarpc.com"); 28 | vm.selectFork(mainnetFork); 29 | proxy = ClustersHubBeta(0x00000000000E1A99dDDd5610111884278BDBda1D); 30 | drop = GasliteDrop(0x09350F89e2D7B6e96bA730783c2d76137B045FEF); 31 | } 32 | 33 | function run() public { 34 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 35 | address deployer = vm.addr(deployerPrivateKey); 36 | console2.log(deployer); 37 | require(deployer == 0x443eDFF556D8fa8BfD69c3943D6eaf34B6a048e0, "wrong addy"); 38 | 39 | /// BATCH BIDDING /// 40 | 41 | // uint256 SIZE = 2; 42 | // uint256[] memory amounts = new uint256[](SIZE); 43 | // bytes32[] memory names = new bytes32[](SIZE); 44 | // for (uint256 i = 0; i < SIZE; i++) { 45 | // amounts[i] = 0.01 ether; 46 | // } 47 | 48 | // names[0] = "name0"; 49 | // names[1] = "name1"; 50 | 51 | // bytes memory data = 52 | // abi.encodeWithSelector(proxy.placeBids.selector, amounts, names); 53 | 54 | // bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, uint128(0.02 ether)); 55 | 56 | // uint256 totalValue = SIZE * 0.01 ether; 57 | // totalValue = 0.20 ether; 58 | // proxy.placeBids{value: totalValue}(amounts, names); 59 | 60 | // (uint256 nativeFee,) = endpoint.quote(40121, data, options, false); 61 | // endpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 62 | 63 | /// BATCH REFUNDING /// 64 | 65 | uint256 SIZE = 2; 66 | address[] memory refundAddresses = new address[](SIZE); 67 | uint256[] memory refundAmounts = new uint256[](SIZE); 68 | 69 | // refundAddresses[0] = address(0x0000000000000000000000004592960cf42342085c7d15afb52fcfe4c698a9b9); 70 | // refundAmounts[0] = 10000000000000000; 71 | // refundAddresses[1] = address(0x0000000000000000000000002ef893e5c362ac25bef41c5a7ef2e046475c8678); 72 | // refundAmounts[1] = 10000000000000000; 73 | // refundAddresses[2] = address(0x0000000000000000000000001421153b3c62ae5ae038a76c940a28b0fce427e3); 74 | // refundAmounts[2] = 10000000000000000; 75 | // refundAddresses[3] = address(0x0000000000000000000000004dde2aadbde7031ce4ca2aa45ae0cabc0f90d242); 76 | // refundAmounts[3] = 10000000000000000; 77 | 78 | refundAddresses[0] = address(0x000000000000000000000000e6899b76a2f01de328954245947b2568a68e0f9f); 79 | refundAmounts[0] = 10000000000000000; 80 | refundAddresses[1] = address(0x0000000000000000000000006e105228c8c0510e5c68e04b3b92f047af56189e); 81 | refundAmounts[1] = 10000000000000000; 82 | 83 | uint256 totalRefund = 0; 84 | for (uint256 i = 0; i < SIZE; i++) { 85 | totalRefund += refundAmounts[i]; 86 | } 87 | 88 | vm.startBroadcast(deployerPrivateKey); 89 | uint256 prodTotalRefund = 0.02 ether; 90 | require(prodTotalRefund == totalRefund, "bad total"); 91 | drop.airdropETH{value: totalRefund}(refundAddresses, refundAmounts); 92 | 93 | vm.stopBroadcast(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /script/Clusters.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console2} from "forge-std/Script.sol"; 5 | 6 | import {LibClone} from "solady/utils/LibClone.sol"; 7 | import {PricingHarberger} from "../src/PricingHarberger.sol"; 8 | import {IPricing} from "../src/interfaces/IPricing.sol"; 9 | import {Endpoint} from "../src/Endpoint.sol"; 10 | import {IEndpoint} from "../src/interfaces/IEndpoint.sol"; 11 | import {ClustersHub} from "../src/ClustersHub.sol"; 12 | 13 | interface IInitialize { 14 | function initialize(address owner_, uint256 protocolDeployTimestamp_) external; 15 | } 16 | 17 | contract ClustersScript is Script { 18 | address constant SIGNER = address(uint160(uint256(keccak256(abi.encodePacked("SIGNER"))))); 19 | address constant LAYERZERO = address(uint160(uint256(keccak256(abi.encodePacked("LAYERZERO"))))); 20 | 21 | function setUp() public {} 22 | 23 | function run() public { 24 | vm.startBroadcast(); 25 | PricingHarberger pricing = new PricingHarberger(); 26 | IPricing pricingProxy = IPricing(LibClone.deployERC1967(address(pricing))); 27 | PricingHarberger(address(pricingProxy)).initialize(msg.sender, block.timestamp + 7 days); 28 | Endpoint endpoint = new Endpoint(); 29 | IEndpoint endpointProxy = IEndpoint(LibClone.deployERC1967(address(endpoint))); 30 | Endpoint(address(endpointProxy)).initialize(msg.sender, SIGNER, LAYERZERO); 31 | ClustersHub clusters = new ClustersHub(address(pricingProxy), address(endpointProxy), block.timestamp + 7 days); 32 | endpointProxy.setClustersAddr(address(clusters)); 33 | vm.stopBroadcast(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /script/Deploy.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Script, console2} from "forge-std/Script.sol"; 5 | import {LibClone} from "solady/utils/LibClone.sol"; 6 | import {LibString} from "solady/utils/LibString.sol"; 7 | import {EnumerableSet} from "openzeppelin/contracts/utils/structs/EnumerableSet.sol"; 8 | import {Ownable} from "solady/auth/Ownable.sol"; 9 | import {PricingFlat} from "../src/PricingFlat.sol"; 10 | import {PricingHarberger} from "../src/PricingHarberger.sol"; 11 | import {Endpoint} from "../src/Endpoint.sol"; 12 | import {ClustersHub} from "../src/ClustersHub.sol"; 13 | import {IPricing} from "../src/interfaces/IPricing.sol"; 14 | import {IEndpoint} from "../src/interfaces/IEndpoint.sol"; 15 | import {IOAppCore} from "layerzero-oapp/contracts/oapp/interfaces/IOAppCore.sol"; 16 | 17 | contract DeployScript is Script { 18 | using LibString for string; 19 | using LibString for uint256; 20 | using LibString for address; 21 | using EnumerableSet for EnumerableSet.UintSet; 22 | 23 | struct Deployment { 24 | string chain; 25 | address pricingTemplate; 26 | address pricingProxy; 27 | address endpointTemplate; 28 | address endpointProxy; 29 | address clusters; 30 | address layerzero; 31 | uint32 dstEid; 32 | } 33 | 34 | uint256 internal deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 35 | address internal deployer = vm.addr(deployerPrivateKey); 36 | address internal constant signer = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; 37 | 38 | EnumerableSet.UintSet internal forkIds; 39 | mapping(uint256 forkId => Deployment) internal deployments; 40 | 41 | uint32 internal constant LZ_EID_SEPOLIA = 40161; 42 | uint32 internal constant LZ_EID_HOLESKY = 40217; 43 | 44 | address internal constant LZ_END_SEPOLIA = 0x464570adA09869d8741132183721B4f0769a0287; 45 | address internal constant LZ_END_HOLESKY = 0x464570adA09869d8741132183721B4f0769a0287; 46 | 47 | function _addressToBytes32(address addr) internal pure returns (bytes32) { 48 | return bytes32(uint256(uint160(addr))); 49 | } 50 | 51 | function setUp() public {} 52 | 53 | function deploy( 54 | string memory chain, 55 | string memory rpcUrl, 56 | bool isHarberger, 57 | bool isHub, 58 | uint32 dstEid, 59 | address lzEndpoint 60 | ) internal { 61 | // Prep fork 62 | uint256 forkId = vm.createFork(rpcUrl); 63 | forkIds.add(forkId); 64 | vm.selectFork(forkId); 65 | vm.startBroadcast(deployerPrivateKey); 66 | // Deploy and initialize pricing 67 | if (isHub) { 68 | if (isHarberger) { 69 | PricingHarberger pricing = new PricingHarberger(); 70 | deployments[forkId].pricingTemplate = address(pricing); 71 | } else { 72 | PricingFlat pricing = new PricingFlat(); 73 | deployments[forkId].pricingTemplate = address(pricing); 74 | } 75 | vm.label(deployments[forkId].pricingTemplate, chain.concat(" Pricing Template")); 76 | deployments[forkId].pricingProxy = LibClone.deployERC1967(deployments[forkId].pricingTemplate); 77 | vm.label(deployments[forkId].pricingProxy, chain.concat(" Pricing Proxy")); 78 | if (isHarberger) { 79 | PricingHarberger(deployments[forkId].pricingProxy).initialize(deployer, block.timestamp); 80 | } else { 81 | PricingFlat(deployments[forkId].pricingProxy).initialize(deployer); 82 | } 83 | } 84 | // Deploy and initialize endpoint 85 | deployments[forkId].endpointTemplate = address(new Endpoint()); 86 | vm.label(deployments[forkId].endpointTemplate, chain.concat(" Endpoint Template")); 87 | deployments[forkId].endpointProxy = LibClone.deployERC1967(deployments[forkId].endpointTemplate); 88 | vm.label(deployments[forkId].endpointProxy, chain.concat(" Endpoint Proxy")); 89 | Endpoint(deployments[forkId].endpointProxy).initialize(deployer, signer, lzEndpoint); 90 | if (isHub) { 91 | deployments[forkId].clusters = address( 92 | new ClustersHub( 93 | deployments[forkId].pricingProxy, deployments[forkId].endpointProxy, block.timestamp + 5 minutes 94 | ) 95 | ); 96 | vm.label(deployments[forkId].clusters, chain.concat(" Clusters Hub")); 97 | IEndpoint(deployments[forkId].endpointProxy).setClustersAddr(deployments[forkId].clusters); 98 | } else { 99 | // Deploy Spoke infrastructure here 100 | } 101 | // Store remaining deployment information 102 | deployments[forkId].chain = chain; 103 | deployments[forkId].layerzero = lzEndpoint; 104 | deployments[forkId].dstEid = dstEid; 105 | vm.stopBroadcast(); 106 | } 107 | 108 | function configure() internal { 109 | uint256[] memory forks = forkIds.values(); 110 | for (uint256 i; i < forks.length; ++i) { 111 | address endpointProxy = deployments[i].endpointProxy; 112 | vm.selectFork(forks[i]); 113 | vm.startBroadcast(deployerPrivateKey); 114 | for (uint256 j; j < forks.length; ++j) { 115 | if (i == j) continue; 116 | IOAppCore(endpointProxy).setPeer( 117 | deployments[forks[j]].dstEid, _addressToBytes32(deployments[forks[j]].endpointProxy) 118 | ); 119 | } 120 | if (i == 0) { 121 | // Set dstEid on hub to enable replication 122 | } else { 123 | IEndpoint(endpointProxy).setDstEid(deployments[forks[0]].dstEid); 124 | } 125 | vm.stopBroadcast(); 126 | } 127 | } 128 | 129 | function run() public { 130 | deploy("Sepolia", vm.envString("SEPOLIA_RPC_URL"), true, true, LZ_EID_SEPOLIA, LZ_END_SEPOLIA); 131 | deploy("Holesky", vm.envString("HOLESKY_RPC_URL"), true, false, LZ_EID_HOLESKY, LZ_END_HOLESKY); 132 | configure(); 133 | 134 | for (uint256 i; i < forkIds.length(); ++i) { 135 | Deployment memory deployment = deployments[forkIds.at(i)]; 136 | if (deployment.pricingTemplate != address(0)) { 137 | console2.log( 138 | deployment.chain.concat(" Pricing Template: ").concat( 139 | deployment.pricingTemplate.toHexStringChecksummed() 140 | ) 141 | ); 142 | } 143 | if (deployment.pricingProxy != address(0)) { 144 | console2.log( 145 | deployment.chain.concat(" Pricing Proxy: ").concat(deployment.pricingProxy.toHexStringChecksummed()) 146 | ); 147 | } 148 | console2.log( 149 | deployment.chain.concat(" Endpoint Template: ").concat( 150 | deployment.endpointTemplate.toHexStringChecksummed() 151 | ) 152 | ); 153 | console2.log( 154 | deployment.chain.concat(" Endpoint Proxy: ").concat(deployment.endpointProxy.toHexStringChecksummed()) 155 | ); 156 | if (deployment.clusters != address(0)) { 157 | console2.log( 158 | deployment.chain.concat(" Clusters: ").concat(deployment.clusters.toHexStringChecksummed()) 159 | ); 160 | } 161 | console2.log( 162 | deployment.chain.concat(" LayerZero Endpoint: ").concat(deployment.layerzero.toHexStringChecksummed()) 163 | ); 164 | console2.log(deployment.chain.concat(" LayerZero DstEid: ").concat(uint256(deployment.dstEid).toString())); 165 | console2.log(""); 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /script/SetConfig.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | pragma solidity ^0.8.23; 3 | 4 | import {Script} from "forge-std/Script.sol"; 5 | import {console2} from "forge-std/console2.sol"; 6 | 7 | interface IEndpointContract { 8 | struct SetConfigParam { 9 | uint32 dstEid; 10 | uint32 configType; 11 | bytes config; 12 | } 13 | 14 | struct UlnConfig { 15 | uint64 confirmations; 16 | uint8 requiredDVNCount; 17 | uint8 optionalDVNCount; 18 | uint8 optionalDVNThreshold; 19 | address[] requiredDVNs; 20 | address[] optionalDVNs; 21 | } 22 | 23 | struct ExecutorConfig { 24 | uint32 maxMessageSize; 25 | address executorAddress; 26 | } 27 | 28 | function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external; 29 | } 30 | 31 | contract SetConfigScript is Script { 32 | // MAKE SURE ALL FOLLOWING VALUES ARE UP TO DATE 33 | // https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts 34 | // https://docs.layerzero.network/v2/developers/evm/technical-reference/dvn-addresses 35 | 36 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 37 | 38 | string originRpcUrl = "https://blast-rpc.publicnode.com"; 39 | address originOAppAddress = 0x00000000000E1A99dDDd5610111884278BDBda1D; 40 | address originEndpoint = 0x1a44076050125825900e736c501f859c50fE728c; 41 | address originSendLib = 0xc1B621b18187F74c8F6D52a6F709Dd2780C09821; 42 | address originReceiveLib = 0x377530cdA84DFb2673bF4d145DCF0C4D7fdcB5b6; 43 | address originExecutor = 0x4208D6E27538189bB48E603D6123A94b8Abe0A0b; 44 | address originDVN = 0xc097ab8CD7b053326DFe9fB3E3a31a0CCe3B526f; 45 | uint32 originEid = 30243; 46 | 47 | string destRpcUrl = "https://eth.llamarpc.com"; 48 | address destOAppAddress = 0x00000000000E1A99dDDd5610111884278BDBda1D; 49 | address destEndpoint = 0x1a44076050125825900e736c501f859c50fE728c; 50 | address destSendLib = 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1; 51 | address destReceiveLib = 0xc02Ab410f0734EFa3F14628780e6e695156024C2; 52 | address destExecutor = 0x173272739Bd7Aa6e4e214714048a9fE699453059; 53 | address destDVN = 0x589dEDbD617e0CBcB916A9223F4d1300c294236b; 54 | uint32 destEid = 30101; 55 | 56 | uint32 maxMessageSize = 16384; 57 | uint64 confirmations = 15; 58 | uint8 requiredDVNCount = 1; 59 | uint8 optionalDVNCount = 0; 60 | uint8 optionalDVNThreshold = 0; 61 | 62 | function setOriginSendConfig() public { 63 | IEndpointContract endpointContract = IEndpointContract(originEndpoint); 64 | 65 | // ULN Configuration 66 | address[] memory requiredDVNs = new address[](1); 67 | requiredDVNs[0] = originDVN; 68 | address[] memory optionalDVNs = new address[](0); 69 | bytes memory ulnConfig = abi.encode( 70 | IEndpointContract.UlnConfig( 71 | confirmations, requiredDVNCount, optionalDVNCount, optionalDVNThreshold, requiredDVNs, optionalDVNs 72 | ) 73 | ); 74 | 75 | // Executor Configuration 76 | bytes memory executorConfig = abi.encode(IEndpointContract.ExecutorConfig(maxMessageSize, destExecutor)); 77 | 78 | IEndpointContract.SetConfigParam[] memory params = new IEndpointContract.SetConfigParam[](2); 79 | params[0] = IEndpointContract.SetConfigParam({dstEid: destEid, configType: 2, config: ulnConfig}); 80 | params[1] = IEndpointContract.SetConfigParam({dstEid: destEid, configType: 1, config: executorConfig}); 81 | 82 | vm.createSelectFork(originRpcUrl); 83 | vm.startBroadcast(deployerPrivateKey); 84 | endpointContract.setConfig(originOAppAddress, originSendLib, params); 85 | vm.stopBroadcast(); 86 | } 87 | 88 | function setOriginReceiveConfig() public { 89 | IEndpointContract endpointContract = IEndpointContract(originEndpoint); 90 | 91 | // ULN Configuration 92 | address[] memory requiredDVNs = new address[](1); 93 | requiredDVNs[0] = originDVN; 94 | address[] memory optionalDVNs = new address[](0); 95 | bytes memory ulnConfig = abi.encode( 96 | IEndpointContract.UlnConfig( 97 | confirmations, requiredDVNCount, optionalDVNCount, optionalDVNThreshold, requiredDVNs, optionalDVNs 98 | ) 99 | ); 100 | 101 | IEndpointContract.SetConfigParam[] memory params = new IEndpointContract.SetConfigParam[](1); 102 | params[0] = IEndpointContract.SetConfigParam({dstEid: destEid, configType: 2, config: ulnConfig}); 103 | 104 | vm.createSelectFork(originRpcUrl); 105 | vm.startBroadcast(deployerPrivateKey); 106 | endpointContract.setConfig(originOAppAddress, originReceiveLib, params); 107 | vm.stopBroadcast(); 108 | } 109 | 110 | function setDestSendConfig() public { 111 | IEndpointContract endpointContract = IEndpointContract(destEndpoint); 112 | 113 | // ULN Configuration 114 | address[] memory requiredDVNs = new address[](1); 115 | requiredDVNs[0] = destDVN; 116 | address[] memory optionalDVNs = new address[](0); 117 | bytes memory ulnConfig = abi.encode( 118 | IEndpointContract.UlnConfig( 119 | confirmations, requiredDVNCount, optionalDVNCount, optionalDVNThreshold, requiredDVNs, optionalDVNs 120 | ) 121 | ); 122 | 123 | // Executor Configuration 124 | bytes memory executorConfig = abi.encode(IEndpointContract.ExecutorConfig(maxMessageSize, originExecutor)); 125 | 126 | IEndpointContract.SetConfigParam[] memory params = new IEndpointContract.SetConfigParam[](2); 127 | params[0] = IEndpointContract.SetConfigParam({dstEid: originEid, configType: 2, config: ulnConfig}); 128 | params[1] = IEndpointContract.SetConfigParam({dstEid: originEid, configType: 1, config: executorConfig}); 129 | 130 | vm.createSelectFork(destRpcUrl); 131 | vm.startBroadcast(deployerPrivateKey); 132 | endpointContract.setConfig(destOAppAddress, destSendLib, params); 133 | vm.stopBroadcast(); 134 | } 135 | 136 | function setDestReceiveConfig() public { 137 | IEndpointContract endpointContract = IEndpointContract(destEndpoint); 138 | 139 | // ULN Configuration 140 | address[] memory requiredDVNs = new address[](1); 141 | requiredDVNs[0] = destDVN; 142 | address[] memory optionalDVNs = new address[](0); 143 | bytes memory ulnConfig = abi.encode( 144 | IEndpointContract.UlnConfig( 145 | confirmations, requiredDVNCount, optionalDVNCount, optionalDVNThreshold, requiredDVNs, optionalDVNs 146 | ) 147 | ); 148 | 149 | IEndpointContract.SetConfigParam[] memory params = new IEndpointContract.SetConfigParam[](1); 150 | params[0] = IEndpointContract.SetConfigParam({dstEid: originEid, configType: 2, config: ulnConfig}); 151 | 152 | vm.createSelectFork(destRpcUrl); 153 | vm.startBroadcast(deployerPrivateKey); 154 | endpointContract.setConfig(destOAppAddress, destReceiveLib, params); 155 | vm.stopBroadcast(); 156 | } 157 | 158 | function run() public { 159 | setOriginSendConfig(); 160 | //setOriginReceiveConfig(); // Commenting these out applies minimal config 161 | //setDestSendConfig(); // Commenting these out applies minimal config 162 | setDestReceiveConfig(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /script/SetDVNConfig.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | pragma solidity ^0.8.23; 3 | 4 | import {Script} from "forge-std/Script.sol"; 5 | import {console2} from "forge-std/console2.sol"; 6 | 7 | // Used for setting an explicit DVN config when no default DVN exists (like on Blast) 8 | interface IEndpointContract { 9 | struct SetConfigParam { 10 | uint32 dstEid; 11 | uint32 configType; 12 | bytes config; 13 | } 14 | 15 | struct UlnConfig { 16 | uint64 confirmations; 17 | uint8 requiredDVNCount; 18 | uint8 optionalDVNCount; 19 | uint8 optionalDVNThreshold; 20 | address[] requiredDVNs; 21 | address[] optionalDVNs; 22 | } 23 | 24 | struct ExecutorConfig { 25 | uint32 maxMessageSize; 26 | address executorAddress; 27 | } 28 | 29 | function setConfig(address _oapp, address _lib, SetConfigParam[] calldata _params) external; 30 | } 31 | 32 | interface Singlesig { 33 | function execute(address to, uint256 value, bytes memory data) external returns (bool success); 34 | function owner() external returns (address); 35 | } 36 | 37 | contract SetConfigScript is Script { 38 | // MAKE SURE ALL FOLLOWING VALUES ARE UP TO DATE 39 | // https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts 40 | // https://docs.layerzero.network/v2/developers/evm/technical-reference/dvn-addresses 41 | 42 | // LayerZero does not set default DVNs on pathways with only 1 DVN, must configure manually (aka Blast) 43 | // getUlnConfig() on the SendUln302 contract is where you can see if the default exists 44 | // Must be configured on both source and destination chain 45 | 46 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 47 | 48 | // string originRpcUrl = "https://rpc.taiko.xyz"; 49 | // address originOAppAddress = 0x00000000000E1A99dDDd5610111884278BDBda1D; 50 | // address originEndpoint = 0x1a44076050125825900e736c501f859c50fE728c; 51 | // address originSendLib = 0xc1B621b18187F74c8F6D52a6F709Dd2780C09821; 52 | // address originReceiveLib = 0x377530cdA84DFb2673bF4d145DCF0C4D7fdcB5b6; 53 | // address originExecutor = 0xa20DB4Ffe74A31D17fc24BD32a7DD7555441058e; 54 | // address originDVN = 0x88B27057A9e00c5F05DDa29241027afF63f9e6e0; 55 | // uint32 originEid = 30290; 56 | 57 | string originRpcUrl = "https://phoenix-rpc.plumenetwork.xyz"; 58 | address originOAppAddress = 0x00000000000E1A99dDDd5610111884278BDBda1D; 59 | address originEndpoint = 0xC1b15d3B262bEeC0e3565C11C9e0F6134BdaCB36; 60 | address originSendLib = 0xFe7C30860D01e28371D40434806F4A8fcDD3A098; 61 | address originReceiveLib = 0x5B19bd330A84c049b62D5B0FC2bA120217a18C1C; 62 | address originExecutor = 0x41Bdb4aa4A63a5b2Efc531858d3118392B1A1C3d; 63 | address originDVN = 0x4208D6E27538189bB48E603D6123A94b8Abe0A0b; // This should not be a Dead DVN 64 | uint32 originEid = 30370; 65 | 66 | string destRpcUrl = "https://eth.llamarpc.com"; 67 | address destOAppAddress = 0x00000000000E1A99dDDd5610111884278BDBda1D; 68 | address destEndpoint = 0x1a44076050125825900e736c501f859c50fE728c; 69 | address destSendLib = 0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1; 70 | address destReceiveLib = 0xc02Ab410f0734EFa3F14628780e6e695156024C2; 71 | address destExecutor = 0x173272739Bd7Aa6e4e214714048a9fE699453059; 72 | address destDVN = 0x589dEDbD617e0CBcB916A9223F4d1300c294236b; 73 | uint32 destEid = 30101; 74 | 75 | uint32 maxMessageSize = 16384; 76 | uint64 confirmations = 5; 77 | uint8 requiredDVNCount = 1; 78 | uint8 optionalDVNCount = 0; 79 | uint8 optionalDVNThreshold = 0; 80 | 81 | Singlesig constant sig = Singlesig(0x000000dE1E80ea5a234FB5488fee2584251BC7e8); 82 | 83 | function setOriginSendConfig() public { 84 | IEndpointContract endpointContract = IEndpointContract(originEndpoint); 85 | 86 | // ULN Configuration 87 | address[] memory requiredDVNs = new address[](1); 88 | requiredDVNs[0] = originDVN; 89 | address[] memory optionalDVNs = new address[](0); 90 | bytes memory ulnConfig = abi.encode( 91 | IEndpointContract.UlnConfig( 92 | confirmations, requiredDVNCount, optionalDVNCount, optionalDVNThreshold, requiredDVNs, optionalDVNs 93 | ) 94 | ); 95 | 96 | // Executor Configuration 97 | bytes memory executorConfig = abi.encode(IEndpointContract.ExecutorConfig(maxMessageSize, originExecutor)); 98 | 99 | IEndpointContract.SetConfigParam[] memory params = new IEndpointContract.SetConfigParam[](2); 100 | params[0] = IEndpointContract.SetConfigParam({dstEid: destEid, configType: 2, config: ulnConfig}); 101 | params[1] = IEndpointContract.SetConfigParam({dstEid: destEid, configType: 1, config: executorConfig}); 102 | 103 | vm.createSelectFork(originRpcUrl); 104 | vm.startBroadcast(deployerPrivateKey); 105 | bytes memory data = 106 | abi.encodeWithSelector(endpointContract.setConfig.selector, originOAppAddress, originSendLib, params); 107 | sig.execute(address(endpointContract), 0, data); 108 | // endpointContract.setConfig(originOAppAddress, originSendLib, params); 109 | vm.stopBroadcast(); 110 | } 111 | 112 | function setOriginReceiveConfig() public { 113 | IEndpointContract endpointContract = IEndpointContract(originEndpoint); 114 | 115 | // ULN Configuration 116 | address[] memory requiredDVNs = new address[](2); 117 | requiredDVNs[0] = originDVN; 118 | address[] memory optionalDVNs = new address[](0); 119 | bytes memory ulnConfig = abi.encode( 120 | confirmations, requiredDVNCount, optionalDVNCount, optionalDVNThreshold, requiredDVNs, optionalDVNs 121 | ); 122 | 123 | IEndpointContract.SetConfigParam[] memory params = new IEndpointContract.SetConfigParam[](2); 124 | params[0] = IEndpointContract.SetConfigParam({dstEid: destEid, configType: 2, config: ulnConfig}); 125 | 126 | vm.createSelectFork(originRpcUrl); 127 | vm.startBroadcast(deployerPrivateKey); 128 | endpointContract.setConfig(originOAppAddress, originReceiveLib, params); 129 | vm.stopBroadcast(); 130 | } 131 | 132 | function setDestSendConfig() public { 133 | IEndpointContract endpointContract = IEndpointContract(destEndpoint); 134 | 135 | // ULN Configuration 136 | address[] memory requiredDVNs = new address[](2); 137 | requiredDVNs[0] = destDVN; 138 | address[] memory optionalDVNs = new address[](0); 139 | bytes memory ulnConfig = abi.encode( 140 | confirmations, requiredDVNCount, optionalDVNCount, optionalDVNThreshold, requiredDVNs, optionalDVNs 141 | ); 142 | 143 | // Executor Configuration 144 | bytes memory executorConfig = abi.encode(maxMessageSize, destExecutor); 145 | 146 | IEndpointContract.SetConfigParam[] memory params = new IEndpointContract.SetConfigParam[](2); 147 | params[0] = IEndpointContract.SetConfigParam({dstEid: originEid, configType: 2, config: ulnConfig}); 148 | params[1] = IEndpointContract.SetConfigParam({dstEid: originEid, configType: 1, config: executorConfig}); 149 | 150 | vm.createSelectFork(destRpcUrl); 151 | vm.startBroadcast(deployerPrivateKey); 152 | endpointContract.setConfig(destOAppAddress, destSendLib, params); 153 | vm.stopBroadcast(); 154 | } 155 | 156 | function setDestReceiveConfig() public { 157 | IEndpointContract endpointContract = IEndpointContract(destEndpoint); 158 | 159 | // ULN Configuration 160 | address[] memory requiredDVNs = new address[](1); 161 | requiredDVNs[0] = destDVN; 162 | address[] memory optionalDVNs = new address[](0); 163 | bytes memory ulnConfig = abi.encode( 164 | IEndpointContract.UlnConfig( 165 | confirmations, requiredDVNCount, optionalDVNCount, optionalDVNThreshold, requiredDVNs, optionalDVNs 166 | ) 167 | ); 168 | 169 | IEndpointContract.SetConfigParam[] memory params = new IEndpointContract.SetConfigParam[](1); 170 | params[0] = IEndpointContract.SetConfigParam({dstEid: originEid, configType: 2, config: ulnConfig}); 171 | 172 | vm.createSelectFork(destRpcUrl); 173 | vm.startBroadcast(deployerPrivateKey); 174 | bytes memory data = 175 | abi.encodeWithSelector(endpointContract.setConfig.selector, destOAppAddress, destReceiveLib, params); 176 | sig.execute(address(endpointContract), 0, data); 177 | // endpointContract.setConfig(destOAppAddress, destReceiveLib, params); 178 | vm.stopBroadcast(); 179 | } 180 | 181 | function run() public { 182 | setOriginSendConfig(); 183 | //setOriginReceiveConfig(); // Commenting these out applies minimal config 184 | //setDestSendConfig(); // Commenting these out applies minimal config 185 | setDestReceiveConfig(); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /script/Testnet.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console2} from "forge-std/Script.sol"; 5 | 6 | import {LibClone} from "solady/utils/LibClone.sol"; 7 | import {PricingHarberger} from "../src/PricingHarberger.sol"; 8 | import {IPricing} from "../src/interfaces/IPricing.sol"; 9 | import {Endpoint} from "../src/Endpoint.sol"; 10 | import {IEndpoint} from "../src/interfaces/IEndpoint.sol"; 11 | import {IOAppCore} from "layerzero-oapp/contracts/oapp/interfaces/IOAppCore.sol"; 12 | import {ClustersHub} from "../src/ClustersHub.sol"; 13 | 14 | contract TestnetScript is Script { 15 | address internal constant SIGNER = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266; 16 | address internal constant LZ_END_SEPOLIA = 0x464570adA09869d8741132183721B4f0769a0287; 17 | address internal constant LZ_END_HOLESKY = 0x464570adA09869d8741132183721B4f0769a0287; 18 | uint32 internal constant LZ_EID_SEPOLIA = 40161; 19 | uint32 internal constant LZ_EID_HOLESKY = 40217; 20 | 21 | string internal SEPOLIA_RPC_URL = vm.envString("SEPOLIA_RPC_URL"); 22 | string internal HOLESKY_RPC_URL = vm.envString("HOLESKY_RPC_URL"); 23 | uint256 internal sepoliaFork; 24 | uint256 internal holeskyFork; 25 | 26 | function addressToBytes32(address addr) internal pure returns (bytes32) { 27 | return bytes32(uint256(uint160(addr))); 28 | } 29 | 30 | function setUp() public { 31 | sepoliaFork = vm.createFork(SEPOLIA_RPC_URL); 32 | holeskyFork = vm.createFork(HOLESKY_RPC_URL); 33 | } 34 | 35 | function run() public { 36 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 37 | address deployer = vm.addr(deployerPrivateKey); 38 | 39 | // Sepolia Hub Deployment 40 | vm.selectFork(sepoliaFork); 41 | vm.startBroadcast(deployerPrivateKey); 42 | PricingHarberger sepoliaPricing = new PricingHarberger(); 43 | vm.label(address(sepoliaPricing), "Sepolia Pricing Template"); 44 | IPricing sepoliaPricingProxy = IPricing(LibClone.deployERC1967(address(sepoliaPricing))); 45 | vm.label(address(sepoliaPricingProxy), "Sepolia Pricing Proxy"); 46 | PricingHarberger(address(sepoliaPricingProxy)).initialize(msg.sender, block.timestamp + 7 days); 47 | Endpoint sepoliaEndpoint = new Endpoint(); 48 | vm.label(address(sepoliaEndpoint), "Sepolia Endpoint Template"); 49 | IEndpoint sepoliaEndpointProxy = IEndpoint(LibClone.deployERC1967(address(sepoliaEndpoint))); 50 | vm.label(address(sepoliaEndpointProxy), "Sepolia Endpoint Proxy"); 51 | Endpoint(address(sepoliaEndpointProxy)).initialize(deployer, SIGNER, LZ_END_SEPOLIA); 52 | ClustersHub sepoliaClusters = 53 | new ClustersHub(address(sepoliaPricingProxy), address(sepoliaEndpointProxy), block.timestamp + 5 minutes); 54 | vm.label(address(sepoliaClusters), "Sepolia Clusters Hub"); 55 | sepoliaEndpointProxy.setClustersAddr(address(sepoliaClusters)); 56 | vm.stopBroadcast(); 57 | 58 | // Holesky Spoke Deployment 59 | vm.selectFork(holeskyFork); 60 | vm.startBroadcast(deployerPrivateKey); 61 | //PricingHarberger holeskyPricing = new PricingHarberger(block.timestamp); 62 | //vm.label(address(holeskyPricing), "Holesky Pricing Template"); 63 | //IPricing holeskyPricingProxy = IPricing(LibClone.deployERC1967(address(holeskyPricing))); 64 | //vm.label(address(holeskyPricingProxy), "Holesky Pricing Proxy"); 65 | //PricingHarberger(address(holeskyPricingProxy)).initialize(msg.sender, block.timestamp + 7 days); 66 | Endpoint holeskyEndpoint = new Endpoint(); 67 | vm.label(address(holeskyEndpoint), "Holesky Endpoint Template"); 68 | IEndpoint holeskyEndpointProxy = IEndpoint(LibClone.deployERC1967(address(holeskyEndpoint))); 69 | vm.label(address(holeskyEndpointProxy), "Holesky Endpoint Proxy"); 70 | Endpoint(address(holeskyEndpointProxy)).initialize(deployer, SIGNER, LZ_END_HOLESKY); 71 | //ClustersHub holeskyClusters = new ClustersHub(address(holeskyPricingProxy), address(holeskyEndpointProxy), 72 | // block.timestamp); 73 | //vm.label(address(holeskyClusters), "Holesky Clusters Spoke"); 74 | //holeskyEndpointProxy.setClustersAddr(address(holeskyClusters)); 75 | IOAppCore(address(holeskyEndpointProxy)).setPeer( 76 | LZ_EID_SEPOLIA, addressToBytes32(address(sepoliaEndpointProxy)) 77 | ); 78 | holeskyEndpointProxy.setDstEid(LZ_EID_SEPOLIA); 79 | vm.stopBroadcast(); 80 | 81 | // Post-deployment Sepolia config finalization 82 | vm.selectFork(sepoliaFork); 83 | vm.startBroadcast(deployerPrivateKey); 84 | IOAppCore(address(sepoliaEndpointProxy)).setPeer( 85 | LZ_EID_HOLESKY, addressToBytes32(address(holeskyEndpointProxy)) 86 | ); 87 | // No DstEid is set on Sepolia Hub as that engages replication, which is not yet fully implemented 88 | vm.stopBroadcast(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /script/Testpad.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console2} from "forge-std/Script.sol"; 5 | 6 | import {LibClone} from "solady/utils/LibClone.sol"; 7 | import {Ownable} from "solady/auth/Ownable.sol"; 8 | import {PricingHarberger} from "../src/PricingHarberger.sol"; 9 | import {IPricing} from "../src/interfaces/IPricing.sol"; 10 | import {Endpoint} from "../src/Endpoint.sol"; 11 | import {IEndpoint} from "../src/interfaces/IEndpoint.sol"; 12 | import {IOAppCore} from "layerzero-oapp/contracts/oapp/interfaces/IOAppCore.sol"; 13 | import {ClustersHub} from "../src/ClustersHub.sol"; 14 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 15 | import "../lib/LayerZero-v2/protocol/contracts/EndpointV2.sol"; 16 | 17 | contract TestpadScript is Script { 18 | using OptionsBuilder for bytes; 19 | 20 | address internal constant HOLESKY_ENDPOINT = 0xc986CB24E8422a179D0799511D42275BcB148714; 21 | 22 | string internal HOLESKY_RPC_URL = vm.envString("HOLESKY_RPC_URL"); 23 | uint256 internal holeskyFork; 24 | 25 | /// @dev Convert address to bytes32 26 | function _addressToBytes32(address addr) internal pure returns (bytes32) { 27 | return bytes32(uint256(uint160(addr))); 28 | } 29 | 30 | function setUp() public { 31 | holeskyFork = vm.createFork(HOLESKY_RPC_URL); 32 | } 33 | 34 | function run() public { 35 | uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); 36 | address deployer = vm.addr(deployerPrivateKey); 37 | IEndpoint endpoint = IEndpoint(HOLESKY_ENDPOINT); 38 | IOAppCore lzEndpoint = IOAppCore(HOLESKY_ENDPOINT); 39 | 40 | vm.selectFork(holeskyFork); 41 | bytes memory data = abi.encodeWithSignature( 42 | "buyName(bytes32,uint256,string)", _addressToBytes32(deployer), 0.01 ether, "zodomo" 43 | ); 44 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(300_000, uint128(0.01 ether)); 45 | (uint256 nativeFee,) = endpoint.quote(40161, data, options, false); 46 | bytes memory results = endpoint.lzSend{value: nativeFee}(data, options, deployer); 47 | 48 | console2.log(nativeFee); 49 | console2.log(endpoint.dstEid()); 50 | console2.log(deployer); 51 | console2.log(Ownable(HOLESKY_ENDPOINT).owner()); 52 | console2.logBytes32(lzEndpoint.peers(40161)); 53 | console2.logBytes(results); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/BasicUUPSImpl.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; 5 | 6 | contract BasicUUPSImpl is UUPSUpgradeable { 7 | error Unauthorized(); 8 | 9 | function _authorizeUpgrade(address newImplementation) internal override { 10 | if (msg.sender != 0x000000dE1E80ea5a234FB5488fee2584251BC7e8) revert Unauthorized(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/ClustersHub.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | // Follow https://docs.soliditylang.org/en/latest/style-guide.html for style 5 | 6 | import {EnumerableSetLib} from "./EnumerableSetLib.sol"; 7 | 8 | import {NameManagerHub} from "./NameManagerHub.sol"; 9 | 10 | import {IClustersHub} from "./interfaces/IClustersHub.sol"; 11 | 12 | import {IEndpoint} from "./interfaces/IEndpoint.sol"; 13 | 14 | import {console2} from "forge-std/Test.sol"; 15 | 16 | contract ClustersHub is NameManagerHub { 17 | using EnumerableSetLib for EnumerableSetLib.Bytes32Set; 18 | 19 | uint256 public nextClusterId = 1; 20 | 21 | /// @dev Enumerates all unverified addresses in a cluster 22 | mapping(uint256 clusterId => EnumerableSetLib.Bytes32Set addrs) internal _unverifiedAddresses; 23 | 24 | /// @dev Enumerates all verified addresses in a cluster 25 | mapping(uint256 clusterId => EnumerableSetLib.Bytes32Set addrs) internal _verifiedAddresses; 26 | 27 | constructor(address pricing_, address endpoint_, uint256 marketOpenTimestamp_) 28 | NameManagerHub(pricing_, endpoint_, marketOpenTimestamp_) 29 | {} 30 | 31 | /// EXTERNAL FUNCTIONS /// 32 | 33 | /// @dev For payable multicall to be secure, we cannot trust msg.value params in other external methods 34 | /// @dev Must instead do strict protocol invariant checking at the end of methods like Uniswap V2 35 | function multicall(bytes[] calldata data) external payable returns (bytes[] memory results) { 36 | _inMulticall = true; 37 | results = new bytes[](data.length); 38 | bool success; 39 | 40 | // Iterate through each call 41 | for (uint256 i = 0; i < data.length; ++i) { 42 | //slither-disable-next-line calls-loop,delegatecall-loop 43 | (success, results[i]) = address(this).delegatecall(data[i]); 44 | if (!success) revert MulticallFailed(); 45 | } 46 | 47 | _checkInvariant(); 48 | 49 | bytes memory payload = abi.encodeWithSignature("multicall(bytes[])", results); 50 | IEndpoint(endpoint).sendPayload{value: msg.value}(payload); 51 | _inMulticall = false; 52 | } 53 | 54 | function add(bytes32 addr) external payable returns (bytes memory payload) { 55 | return add(_addressToBytes32(msg.sender), addr); 56 | } 57 | 58 | function verify(uint256 clusterId) external payable returns (bytes memory payload) { 59 | return verify(_addressToBytes32(msg.sender), clusterId); 60 | } 61 | 62 | function remove(bytes32 addr) external payable returns (bytes memory payload) { 63 | return remove(_addressToBytes32(msg.sender), addr); 64 | } 65 | 66 | function getUnverifiedAddresses(uint256 clusterId) external view returns (bytes32[] memory) { 67 | return _unverifiedAddresses[clusterId].values(); 68 | } 69 | 70 | function getVerifiedAddresses(uint256 clusterId) external view returns (bytes32[] memory) { 71 | return _verifiedAddresses[clusterId].values(); 72 | } 73 | 74 | function noBridgeFundsReturn() external payable { 75 | if (msg.sender != endpoint) revert Unauthorized(); 76 | } 77 | 78 | /// ENDPOINT FUNCTIONS /// 79 | 80 | function add(bytes32 msgSender, bytes32 addr) 81 | public 82 | payable 83 | onlyEndpoint(msgSender) 84 | returns (bytes memory payload) 85 | { 86 | uint256 clusterId = addressToClusterId[msgSender]; 87 | if (clusterId == 0) revert NoCluster(); 88 | if (_verifiedAddresses[clusterId].contains(addr)) revert Registered(); 89 | _add(addr, clusterId); 90 | 91 | payload = abi.encodeWithSignature("add(bytes32,bytes32)", msgSender, addr); 92 | if (_inMulticall) return payload; 93 | else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); 94 | } 95 | 96 | function verify(bytes32 msgSender, uint256 clusterId) 97 | public 98 | payable 99 | onlyEndpoint(msgSender) 100 | returns (bytes memory payload) 101 | { 102 | if (!_unverifiedAddresses[clusterId].contains(msgSender)) revert Unauthorized(); 103 | uint256 currentClusterId = addressToClusterId[msgSender]; 104 | if (currentClusterId != 0) { 105 | // If msgSender is the last address in their cluster, take all of their names with them 106 | if (_verifiedAddresses[currentClusterId].length() == 1) { 107 | bytes32[] memory names = _clusterNames[currentClusterId].values(); 108 | for (uint256 i; i < names.length; ++i) { 109 | _transferName(names[i], currentClusterId, clusterId); 110 | } 111 | } 112 | _remove(msgSender, currentClusterId); 113 | } 114 | _verify(msgSender, clusterId); 115 | 116 | payload = abi.encodeWithSignature("verify(bytes32,uint256)", msgSender, clusterId); 117 | if (_inMulticall) return payload; 118 | else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); 119 | } 120 | 121 | function remove(bytes32 msgSender, bytes32 addr) 122 | public 123 | payable 124 | onlyEndpoint(msgSender) 125 | returns (bytes memory payload) 126 | { 127 | uint256 clusterId = addressToClusterId[msgSender]; 128 | if (clusterId == 0) revert NoCluster(); 129 | // If the cluster has valid names, prevent removing final address, regardless of what is supplied for addr 130 | if (_verifiedAddresses[clusterId].contains(addr)) { 131 | if (_clusterNames[clusterId].length() > 0 && _verifiedAddresses[clusterId].length() == 1) revert Invalid(); 132 | } 133 | _remove(addr, clusterId); 134 | 135 | payload = abi.encodeWithSignature("remove(bytes32,bytes32)", msgSender, addr); 136 | if (_inMulticall) return payload; 137 | else IEndpoint(endpoint).sendPayload{value: msg.value}(payload); 138 | } 139 | 140 | /// INTERNAL FUNCTIONS /// 141 | 142 | function _add(bytes32 addr, uint256 clusterId) internal { 143 | _unverifiedAddresses[clusterId].add(addr); 144 | emit Add(clusterId, addr); 145 | } 146 | 147 | function _verify(bytes32 addr, uint256 clusterId) internal { 148 | _unverifiedAddresses[clusterId].remove(addr); 149 | _verifiedAddresses[clusterId].add(addr); 150 | addressToClusterId[addr] = clusterId; 151 | emit Verify(clusterId, addr); 152 | } 153 | 154 | function _remove(bytes32 addr, uint256 clusterId) internal { 155 | _unverifiedAddresses[clusterId].remove(addr); 156 | if (addressToClusterId[addr] == clusterId) { 157 | delete addressToClusterId[addr]; 158 | _verifiedAddresses[clusterId].remove(addr); 159 | bytes32 walletName = reverseLookup[addr]; 160 | if (walletName != bytes32("")) { 161 | delete forwardLookup[clusterId][walletName]; 162 | delete reverseLookup[addr]; 163 | } 164 | } 165 | emit Remove(clusterId, addr); 166 | } 167 | 168 | function _hookCreate(bytes32 addr) internal override { 169 | uint256 clusterId = nextClusterId++; 170 | _verifiedAddresses[clusterId].add(addr); 171 | addressToClusterId[addr] = clusterId; 172 | } 173 | 174 | function _hookDelete(uint256 clusterId) internal override { 175 | bytes32[] memory addresses = _verifiedAddresses[clusterId].values(); 176 | for (uint256 i; i < addresses.length; ++i) { 177 | _remove(addresses[i], clusterId); 178 | } 179 | emit Delete(clusterId); 180 | } 181 | 182 | function _hookCheck(uint256 clusterId) internal view override { 183 | if (clusterId == 0) return; 184 | if (_verifiedAddresses[clusterId].length() == 0) revert Invalid(); 185 | } 186 | 187 | function _hookCheck(uint256 clusterId, bytes32 addr) internal view override { 188 | if (!_unverifiedAddresses[clusterId].contains(addr) && clusterId != addressToClusterId[addr]) { 189 | revert Unauthorized(); 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/EnumerableSetLib.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | library EnumerableSetLib { 5 | error ValueCannotBeZero(); 6 | 7 | struct Bytes32 { 8 | bytes32 value; 9 | } 10 | 11 | struct Uint256 { 12 | uint256 value; 13 | } 14 | 15 | struct Bytes32Set { 16 | mapping(uint256 => Bytes32) _values; 17 | mapping(bytes32 => Uint256) _positions; 18 | uint256 _length; 19 | } 20 | 21 | function length(Bytes32Set storage set) internal view returns (uint256) { 22 | uint256 n = set._length; 23 | if (n == 0) { 24 | if (set._values[0].value == bytes32(0)) return 0; 25 | if (set._values[1].value == bytes32(0)) return 1; 26 | if (set._values[2].value == bytes32(0)) return 2; 27 | return 3; 28 | } 29 | return uint256(uint248(n)); 30 | } 31 | 32 | function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) { 33 | if (value == bytes32(0)) revert ValueCannotBeZero(); 34 | 35 | if (set._length == 0) { 36 | if (set._values[0].value == value) return true; 37 | if (set._values[1].value == value) return true; 38 | if (set._values[2].value == value) return true; 39 | return false; 40 | } 41 | return set._positions[value].value != 0; 42 | } 43 | 44 | function add(Bytes32Set storage set, bytes32 value) internal returns (bool result) { 45 | if (value == bytes32(0)) revert ValueCannotBeZero(); 46 | uint256 n = set._length; 47 | if (n == 0) { 48 | Bytes32 storage p0 = set._values[0]; 49 | bytes32 v0 = p0.value; 50 | if (v0 == bytes32(0)) { 51 | p0.value = value; 52 | return true; 53 | } else if (v0 == value) { 54 | return false; 55 | } 56 | Bytes32 storage p1 = set._values[1]; 57 | bytes32 v1 = p1.value; 58 | if (v1 == bytes32(0)) { 59 | p1.value = value; 60 | return true; 61 | } else if (v1 == value) { 62 | return false; 63 | } 64 | Bytes32 storage p2 = set._values[2]; 65 | bytes32 v2 = p2.value; 66 | if (v2 == bytes32(0)) { 67 | p2.value = value; 68 | set._positions[v0].value = 1; 69 | set._positions[v1].value = 2; 70 | set._positions[value].value = 3; 71 | set._length = 3 | (1 << 255); 72 | return true; 73 | } else if (v2 == value) { 74 | return false; 75 | } 76 | } 77 | Uint256 storage q = set._positions[value]; 78 | uint256 i = q.value; 79 | if (i == 0) { 80 | set._values[uint256(uint248(n))].value = value; 81 | unchecked { 82 | n += 1; 83 | } 84 | q.value = uint256(uint248(n)); 85 | set._length = n; 86 | return true; 87 | } 88 | } 89 | 90 | function remove(Bytes32Set storage set, bytes32 value) internal returns (bool result) { 91 | if (value == bytes32(0)) revert ValueCannotBeZero(); 92 | 93 | unchecked { 94 | uint256 n = set._length; 95 | if (n == 0) { 96 | Bytes32 storage p0 = set._values[0]; 97 | bytes32 v0 = p0.value; 98 | Bytes32 storage p1 = set._values[1]; 99 | if (v0 == bytes32(0)) { 100 | return false; 101 | } else if (v0 == value) { 102 | p0.value = p1.value; 103 | p1.value = bytes32(0); 104 | return true; 105 | } 106 | bytes32 v1 = p1.value; 107 | if (v1 == bytes32(0)) { 108 | return false; 109 | } else if (v1 == value) { 110 | p1.value = bytes32(0); 111 | return true; 112 | } 113 | return false; 114 | } 115 | 116 | Uint256 storage p = set._positions[value]; 117 | uint256 position = p.value; 118 | if (position == 0) { 119 | return false; 120 | } 121 | uint256 valueIndex = position - 1; 122 | uint256 j = n - 1; 123 | uint256 lastIndex = uint256(uint248(j)); 124 | Bytes32 storage last = set._values[lastIndex]; 125 | if (valueIndex != lastIndex) { 126 | bytes32 lastValue = last.value; 127 | set._values[valueIndex].value = lastValue; 128 | last.value = bytes32(0); 129 | set._positions[lastValue].value = position; 130 | } 131 | set._length = j; 132 | p.value = 0; 133 | return true; 134 | } 135 | } 136 | 137 | function values(Bytes32Set storage set) internal view returns (bytes32[] memory result) { 138 | unchecked { 139 | uint256 n = length(set); 140 | result = new bytes32[](n); 141 | for (uint256 i; i != n; ++i) { 142 | result[i] = set._values[i].value; 143 | } 144 | } 145 | } 146 | 147 | function at(Bytes32Set storage set, uint256 i) internal view returns (bytes32) { 148 | return set._values[i].value; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/PricingFlat.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; 5 | import {Initializable} from "openzeppelin/contracts/proxy/utils/Initializable.sol"; 6 | import {Ownable} from "solady/auth/Ownable.sol"; 7 | import {IPricing} from "./interfaces/IPricing.sol"; 8 | 9 | contract PricingFlat is UUPSUpgradeable, Initializable, Ownable, IPricing { 10 | uint256 public constant minAnnualPrice = 0.01 ether; 11 | 12 | function initialize(address owner_) public initializer { 13 | _initializeOwner(owner_); 14 | } 15 | 16 | function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} 17 | 18 | /// @inheritdoc IPricing 19 | function getIntegratedPrice(uint256, uint256 secondsSinceUpdate) 20 | public 21 | pure 22 | returns (uint256 spent, uint256 price) 23 | { 24 | spent = secondsSinceUpdate * minAnnualPrice / 365 days; 25 | price = minAnnualPrice; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/PricingHarberger.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; 5 | import {Initializable} from "openzeppelin/contracts/proxy/utils/Initializable.sol"; 6 | import {Ownable} from "solady/auth/Ownable.sol"; 7 | import {IPricing} from "./interfaces/IPricing.sol"; 8 | import {FixedPointMathLib as F} from "solady/utils/FixedPointMathLib.sol"; 9 | import {console2} from "forge-std/console2.sol"; 10 | 11 | /* 12 | ECONOMIC MODEL: 13 | min annual price: 0.01 ether 14 | max annual price: 0.02 ether + 0.01 ether per year 15 | max annual price: f'(n) = 0.02 + 0.01n 16 | integral of max annual price, registration fee for n years: f(n) = 0.02n + 0.005n^2 17 | 18 | prices follow an exponential decay: pe^(y*ln(0.5)) where y is the fractional number of years since last bid and p is 19 | price at last bid 20 | bids increase price to bid amount in 1 month, price += ((bidPrice - oldPrice) * months^2) and simply price = bidPrice 21 | for months >= 1 22 | */ 23 | 24 | /// @notice A stateless computation library for price, bids, decays, etc 25 | /// @dev All state is stored in clusters so we can replace the Pricing module while providing guarantees to existing 26 | /// holders 27 | contract PricingHarberger is UUPSUpgradeable, Initializable, Ownable, IPricing { 28 | uint256 internal constant SECONDS_IN_MONTH = 30 days; 29 | uint256 internal constant SECONDS_IN_YEAR = 365 days; 30 | uint256 internal constant DENOMINATOR = 10_000; 31 | uint256 internal constant WAD = 1e18; 32 | int256 internal constant sWAD = 1e18; 33 | 34 | uint256 public constant minAnnualPrice = 0.01 ether; 35 | uint256 public constant maxPriceBase = 0.02 ether; 36 | uint256 public constant maxPriceIncrement = 0.01 ether; 37 | 38 | uint256 public protocolDeployTimestamp; 39 | 40 | function initialize(address owner_, uint256 protocolDeployTimestamp_) public initializer { 41 | _initializeOwner(owner_); 42 | protocolDeployTimestamp = protocolDeployTimestamp_; 43 | } 44 | 45 | function _authorizeUpgrade(address newImplementation) internal override onlyOwner {} 46 | 47 | /// PUBLIC FUNCTIONS /// 48 | 49 | /// @inheritdoc IPricing 50 | function getIntegratedPrice(uint256 lastUpdatedPrice, uint256 secondsSinceUpdate) 51 | public 52 | view 53 | returns (uint256, uint256) 54 | { 55 | uint256 secondsSinceDeployment = block.timestamp - protocolDeployTimestamp; 56 | if (lastUpdatedPrice <= minAnnualPrice) { 57 | // Lower bound 58 | return (minAnnualPrice * secondsSinceUpdate / SECONDS_IN_YEAR, minAnnualPrice); 59 | } else if (lastUpdatedPrice >= getMaxPrice(secondsSinceDeployment)) { 60 | uint256 numYearsUntilMaxPriceWad = 61 | getMaxIntersection(lastUpdatedPrice, secondsSinceDeployment / SECONDS_IN_YEAR); 62 | uint256 numSecondsUntilMaxPrice = numYearsUntilMaxPriceWad * SECONDS_IN_YEAR / WAD; 63 | 64 | if (secondsSinceUpdate <= numSecondsUntilMaxPrice) { 65 | return (getIntegratedMaxPrice(secondsSinceUpdate), getDecayPrice(lastUpdatedPrice, secondsSinceUpdate)); 66 | } else { 67 | uint256 numYearsUntilMinPriceWad = getMinIntersection(lastUpdatedPrice); 68 | uint256 numSecondsUntilMinPrice = numYearsUntilMinPriceWad * SECONDS_IN_YEAR / WAD; 69 | 70 | if (secondsSinceUpdate <= numSecondsUntilMinPrice) { 71 | uint256 integralPart1 = getIntegratedMaxPrice(numSecondsUntilMaxPrice); 72 | uint256 maxPrice1 = getDecayPrice(lastUpdatedPrice, numSecondsUntilMaxPrice); 73 | uint256 integralPart2 = 74 | getIntegratedDecayPrice(maxPrice1, secondsSinceUpdate - numSecondsUntilMaxPrice); 75 | return (integralPart1 + integralPart2, getDecayPrice(lastUpdatedPrice, secondsSinceDeployment)); 76 | } else { 77 | uint256 integralPart1 = getIntegratedMaxPrice(numSecondsUntilMaxPrice); 78 | uint256 maxPrice1 = getDecayPrice(lastUpdatedPrice, numSecondsUntilMaxPrice); 79 | uint256 integralPart2 = 80 | getIntegratedDecayPrice(maxPrice1, numSecondsUntilMinPrice - numSecondsUntilMaxPrice); 81 | uint256 integralPart3 = minAnnualPrice * secondsSinceUpdate / SECONDS_IN_YEAR; 82 | return (integralPart1 + integralPart2 + integralPart3, minAnnualPrice); 83 | } 84 | } 85 | } else { 86 | // Exponential decay from middle range 87 | // Calculate time until intersection with min price 88 | // p0 * e^(t*ln0.5) = minPrice 89 | // t = ln(minPrice/p0) / ln(0.5) 90 | uint256 numYearsUntilMinPriceWad = 91 | uint256(F.rawSDivWad(F.lnWad(int256(F.rawDivWad(minAnnualPrice, lastUpdatedPrice))), F.lnWad(0.5e18))); 92 | uint256 numSecondsUntilMinPrice = numYearsUntilMinPriceWad * SECONDS_IN_YEAR / WAD; 93 | 94 | if (secondsSinceUpdate <= numSecondsUntilMinPrice) { 95 | // Return simple exponential decay integral 96 | return ( 97 | getIntegratedDecayPrice(lastUpdatedPrice, secondsSinceUpdate), 98 | getDecayPrice(lastUpdatedPrice, secondsSinceUpdate) 99 | ); 100 | } else { 101 | (uint256 simpleMinLeftover,) = 102 | getIntegratedPrice(minAnnualPrice, secondsSinceUpdate - numSecondsUntilMinPrice); 103 | return ( 104 | getIntegratedDecayPrice(lastUpdatedPrice, numSecondsUntilMinPrice) + simpleMinLeftover, 105 | minAnnualPrice 106 | ); 107 | } 108 | } 109 | } 110 | 111 | /// INTERNAL FUNCTIONS /// 112 | 113 | /// @return yearsUntilIntersectionWad 114 | function getMaxIntersection(uint256 p, uint256 yearsSinceDeploymentWad) internal pure returns (uint256) { 115 | // Calculate time until lastUpdatedPrice exponential decay intersects with max price positive slope line 116 | // Solve pe^(t*ln(0.5)) = 0.02 + 0.01*(years+t) 117 | // https://www.wolframalpha.com/input?i=pe%5E%28x*ln%280.5%29%29+%3D+0.02+%2B+0.01y+%2B+0.01x%2C+solve+for+x 118 | // Copy paste select 0 branch of Lambert W 119 | // https://www.wolframalpha.com/input?i=-2+-+1+y+%2B+1.4427+ProductLog%280%2C+%28196722032+e%5E%28%2849180508+%282+%2B+y%29%29%2F70952475%29+p%29%2F2838099%29 120 | // Then simplify the large fractions 121 | // https://www.wolframalpha.com/input?i=-2+-+1+y+%2B+1.4427+ProductLog%280%2C+%2869.3147e%5E%280.693147%282+%2B+y%29%29+p%29%29 122 | // Plot at 123 | // https://www.wolframalpha.com/input?i=-2+-+y+%2B+1.4427+ProductLog%280%2C+%2869.3147e%5E%280.693147%282+%2B+y%29%29+p%29%29%2C+plot+for+p+in+%5B0%2C+1%5D+with+y%3D10 124 | // Looks correct, p=x=0.02 yields 0, meaning starting price of 0.02 eth intersects with max price at time 0 125 | // p=x=1 yields ~4, meaning starting price of 1 eth gets cut in half 4 times in 4 years landing at 0.061 126 | // intersecting with 0.02 + 0.01*4years 127 | 128 | return uint256( 129 | F.rawSMulWad( 130 | 1.4427e18, 131 | F.lambertW0Wad( 132 | F.rawSMulWad( 133 | int256(F.rawMulWad(69.3147e18, p)), 134 | F.expWad(int256(F.mulWad(0.693147e18, 2 * WAD + yearsSinceDeploymentWad))) 135 | ) 136 | ) 137 | ) 138 | ) - 2 * WAD - yearsSinceDeploymentWad; 139 | } 140 | 141 | /// @return yearsUntilIntersectionWad 142 | function getMinIntersection(uint256 p) internal pure returns (uint256) { 143 | return uint256(F.rawSDivWad(F.lnWad(int256(F.rawDivWad(minAnnualPrice, p))), F.lnWad(0.5e18))); 144 | } 145 | 146 | /// @notice The annual max price integrated over its duration 147 | function getIntegratedMaxPrice(uint256 numSeconds) internal pure returns (uint256) { 148 | return maxPriceBase * numSeconds / SECONDS_IN_YEAR 149 | + (maxPriceIncrement * numSeconds ** 2) / (2 * SECONDS_IN_YEAR ** 2); 150 | } 151 | 152 | /// @notice The annual max price at an instantaneous point in time, derivative of getIntegratedMaxPrice 153 | function getMaxPrice(uint256 numSeconds) internal pure returns (uint256) { 154 | return maxPriceBase + (maxPriceIncrement * numSeconds) / SECONDS_IN_YEAR; 155 | } 156 | 157 | /// @notice The integral of the annual price while it's exponentially decaying over `numSeconds` starting at p0 158 | function getIntegratedDecayPrice(uint256 p0, uint256 numSeconds) internal pure returns (uint256) { 159 | return F.rawMulWad(p0, uint256(F.rawSDivWad(int256(getDecayMultiplier(numSeconds)) - sWAD, F.lnWad(0.5e18)))); 160 | } 161 | 162 | /// @notice The annual decayed price at an instantaneous point in time, derivative of getIntegratedDecayPrice 163 | function getDecayPrice(uint256 p0, uint256 numSeconds) internal pure returns (uint256) { 164 | return F.rawMulWad(p0, getDecayMultiplier(numSeconds)); 165 | } 166 | 167 | /// @notice Implements e^(ln(0.5)x) ~= e^(-0.6931x) which cuts the number in half every year for exponential decay 168 | /// @dev Since this will be <1, returns a wad with 18 decimals 169 | function getDecayMultiplier(uint256 numSeconds) internal pure returns (uint256) { 170 | return uint256(F.expWad(F.lnWad(0.5e18) * int256(numSeconds) / int256(SECONDS_IN_YEAR))); 171 | } 172 | 173 | /// @notice Current adjusts quadratically up to bid price, capped at 1 month duration 174 | function getPriceAfterBid(uint256 p0, uint256 pBid, uint256 bidLengthInSeconds) internal pure returns (uint256) { 175 | if (p0 >= pBid) return p0; 176 | if (bidLengthInSeconds >= SECONDS_IN_MONTH) return pBid; 177 | uint256 wadMonths = toWadUnsafe(bidLengthInSeconds) / SECONDS_IN_MONTH; 178 | return p0 + F.rawMulWad(pBid - p0, F.rawMulWad(wadMonths, wadMonths)); 179 | } 180 | 181 | function toWadUnsafe(uint256 x) internal pure returns (uint256) { 182 | return F.rawMul(WAD, x); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /src/beta/ClustersHubBeta.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.26; 3 | 4 | import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; 5 | import {Origin, OAppReceiverUpgradeable} from "layerzero-oapp/contracts/oapp-upgradeable/OAppReceiverUpgradeable.sol"; 6 | 7 | contract ClustersHubBeta is UUPSUpgradeable, OAppReceiverUpgradeable { 8 | event Bid(bytes32 from, uint256 amount, bytes32 name); 9 | event Bid(bytes32 from, uint256 amount, bytes32 name, bytes32 referralAddress); 10 | 11 | error BadBatch(); 12 | error BadDelegatecall(); 13 | error Unauthorized(); 14 | 15 | /// UUPSUpgradeable Authentication /// 16 | 17 | function _authorizeUpgrade(address newImplementation) internal override { 18 | if (msg.sender != 0x000000dE1E80ea5a234FB5488fee2584251BC7e8) revert Unauthorized(); 19 | } 20 | 21 | /// OAppReceiver Functions /// 22 | 23 | function initialize(address endpoint_, address owner_) external initializer { 24 | _initializeOAppCore(endpoint_, owner_); 25 | } 26 | 27 | function reinitialize() external reinitializer(2) { 28 | // Unset placeholder 1 val from storage slot 0x2 onchain where tstoreSender used to live pre-dencun 29 | assembly ("memory-safe") { 30 | sstore(2, 0) 31 | } 32 | } 33 | 34 | function _lzReceive( 35 | Origin calldata origin, 36 | bytes32 guid, 37 | bytes calldata message, 38 | address executor, 39 | bytes calldata extraData 40 | ) internal override { 41 | (bytes32 msgsender, bytes memory calldata_) = abi.decode(message, (bytes32, bytes)); 42 | assembly ("memory-safe") { 43 | tstore(0, msgsender) 44 | } 45 | (bool success,) = address(this).delegatecall(calldata_); 46 | if (!success) revert BadDelegatecall(); 47 | assembly ("memory-safe") { 48 | tstore(0, 0) 49 | } 50 | } 51 | 52 | /// Core Logic /// 53 | 54 | function placeBid(bytes32 name) external payable { 55 | bytes32 tstoreSender; 56 | assembly ("memory-safe") { 57 | tstoreSender := tload(0) 58 | } 59 | bytes32 from = tstoreSender == 0 ? bytes32(uint256(uint160(msg.sender))) : tstoreSender; 60 | emit Bid(from, msg.value, name); 61 | } 62 | 63 | function placeBids(uint256[] calldata amounts, bytes32[] calldata names) external payable { 64 | bytes32 tstoreSender; 65 | assembly ("memory-safe") { 66 | tstoreSender := tload(0) 67 | } 68 | bytes32 from = tstoreSender == 0 ? bytes32(uint256(uint160(msg.sender))) : tstoreSender; 69 | if (amounts.length != names.length) revert BadBatch(); 70 | uint256 amountTotal = 0; 71 | for (uint256 i = 0; i < amounts.length; i++) { 72 | amountTotal += amounts[i]; 73 | emit Bid(from, amounts[i], names[i]); 74 | } 75 | if (amountTotal != msg.value) revert BadBatch(); 76 | } 77 | 78 | function placeBid(bytes32 name, bytes32 referralAddress) external payable { 79 | bytes32 tstoreSender; 80 | assembly ("memory-safe") { 81 | tstoreSender := tload(0) 82 | } 83 | bytes32 from = tstoreSender == 0 ? bytes32(uint256(uint160(msg.sender))) : tstoreSender; 84 | emit Bid(from, msg.value, name, referralAddress); 85 | } 86 | 87 | function placeBids(uint256[] calldata amounts, bytes32[] calldata names, bytes32 referralAddress) 88 | external 89 | payable 90 | { 91 | bytes32 tstoreSender; 92 | assembly ("memory-safe") { 93 | tstoreSender := tload(0) 94 | } 95 | bytes32 from = tstoreSender == 0 ? bytes32(uint256(uint160(msg.sender))) : tstoreSender; 96 | if (amounts.length != names.length) revert BadBatch(); 97 | uint256 amountTotal = 0; 98 | for (uint256 i = 0; i < amounts.length; i++) { 99 | amountTotal += amounts[i]; 100 | emit Bid(from, amounts[i], names[i], referralAddress); 101 | } 102 | if (amountTotal != msg.value) revert BadBatch(); 103 | } 104 | 105 | function withdraw(address payable to_, uint256 amount) external { 106 | if (msg.sender != 0x000000dE1E80ea5a234FB5488fee2584251BC7e8) revert Unauthorized(); 107 | to_.call{value: amount}(""); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/beta/ClustersInitiatorBeta.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {UUPSUpgradeable} from "solady/utils/UUPSUpgradeable.sol"; 5 | import { 6 | OAppSenderUpgradeable, MessagingFee 7 | } from "layerzero-oapp/contracts/oapp-upgradeable/OAppSenderUpgradeable.sol"; 8 | 9 | contract ClustersInitiatorBeta is UUPSUpgradeable, OAppSenderUpgradeable { 10 | uint32 public dstEid; 11 | 12 | error Unauthorized(); 13 | 14 | /// UUPSUpgradeable Authentication /// 15 | 16 | function _authorizeUpgrade(address newImplementation) internal override { 17 | if (msg.sender != 0x000000dE1E80ea5a234FB5488fee2584251BC7e8) revert Unauthorized(); 18 | } 19 | 20 | /// OAPPSender Functions /// 21 | 22 | function initialize(address endpoint_, address owner_) external initializer { 23 | _initializeOAppCore(endpoint_, owner_); 24 | } 25 | 26 | function setDstEid(uint32 dstEid_) external onlyOwner { 27 | dstEid = dstEid_; 28 | } 29 | 30 | function quote(bytes memory message, bytes memory options) external view returns (uint256 nativeFee) { 31 | MessagingFee memory msgQuote = _quote(dstEid, message, options, false); 32 | return msgQuote.nativeFee; 33 | } 34 | 35 | function lzSend(bytes memory calldata_, bytes memory options) external payable { 36 | MessagingFee memory fee = MessagingFee({nativeFee: uint128(msg.value), lzTokenFee: 0}); 37 | bytes32 msgsender = bytes32(uint256(uint160(msg.sender))); 38 | _lzSend(dstEid, abi.encode(msgsender, calldata_), options, fee, payable(msg.sender)); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/interfaces/IClustersHub.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | interface IClustersHub { 5 | /// STRUCTS /// 6 | 7 | struct PriceIntegral { 8 | uint256 lastUpdatedTimestamp; 9 | uint256 lastUpdatedPrice; 10 | } 11 | 12 | /// @notice All relevant information for an individual bid 13 | struct Bid { 14 | uint256 ethAmount; 15 | uint256 createdTimestamp; 16 | bytes32 bidder; 17 | } 18 | 19 | /// EVENTS /// 20 | 21 | event Add(uint256 indexed clusterId, bytes32 indexed addr); 22 | event Remove(uint256 indexed clusterId, bytes32 indexed addr); 23 | event Verify(uint256 indexed clusterId, bytes32 indexed addr); 24 | event Delete(uint256 indexed clusterId); 25 | 26 | event BuyName(bytes32 indexed name, uint256 indexed clusterId, uint256 indexed amount); 27 | event FundName(bytes32 indexed name, bytes32 indexed funder, uint256 indexed amount); 28 | event TransferName(bytes32 indexed name, uint256 indexed fromClusterId, uint256 indexed toClusterId); 29 | event PokeName(bytes32 indexed name); 30 | event DefaultClusterName(bytes32 indexed name, uint256 indexed clusterId); 31 | event SetWalletName(bytes32 indexed walletName, bytes32 indexed wallet, uint256 indexed clusterId); 32 | 33 | event BidPlaced(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); 34 | event BidRefunded(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); 35 | event BidIncreased(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); 36 | event BidReduced(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); 37 | event BidRevoked(bytes32 indexed name, bytes32 indexed bidder, uint256 indexed amount); 38 | 39 | /// ERRORS /// 40 | 41 | error NoBid(); 42 | error SelfBid(); 43 | error Invalid(); 44 | error Timelock(); 45 | error LongName(); 46 | error Insolvent(); 47 | error EmptyName(); 48 | error NoCluster(); 49 | error Registered(); 50 | error Unregistered(); 51 | error Unauthorized(); 52 | error Insufficient(); 53 | error BadInvariant(); 54 | error MulticallFailed(); 55 | error NativeTokenTransferFailed(); 56 | 57 | /// STORAGE / VIEW FUNCTIONS /// 58 | 59 | function endpoint() external view returns (address endpoint); 60 | function nextClusterId() external view returns (uint256 clusterId); 61 | function addressToClusterId(bytes32 addr) external view returns (uint256 clusterId); 62 | function nameToClusterId(bytes32 name) external view returns (uint256 clusterId); 63 | function defaultClusterName(uint256 clusterId) external view returns (bytes32 name); 64 | function forwardLookup(uint256 clusterId, bytes32 walletName) external view returns (bytes32 addr); 65 | function reverseLookup(bytes32 addr) external view returns (bytes32 walletName); 66 | 67 | function priceIntegral(bytes32 name) 68 | external 69 | view 70 | returns (uint256 lastUpdatedTimestamp, uint256 lastUpdatedPrice); 71 | function nameBacking(bytes32 name) external view returns (uint256 ethAmount); 72 | function bids(bytes32 name) external view returns (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder); 73 | function bidRefunds(bytes32 bidder) external view returns (uint256 refund); 74 | 75 | function protocolAccrual() external view returns (uint256 accrual); 76 | function totalNameBacking() external view returns (uint256 nameBacking); 77 | function totalBidBacking() external view returns (uint256 bidBacking); 78 | 79 | function getUnverifiedAddresses(uint256 clusterId) external view returns (bytes32[] memory addresses); 80 | function getVerifiedAddresses(uint256 clusterId) external view returns (bytes32[] memory addresses); 81 | function getClusterNamesBytes32(uint256 clusterId) external view returns (bytes32[] memory names); 82 | 83 | /// EXTERNAL FUNCTIONS /// 84 | 85 | function multicall(bytes[] calldata data) external payable returns (bytes[] memory results); 86 | 87 | function add(bytes32 addr) external payable returns (bytes memory); 88 | function add(bytes32 msgSender, bytes32 addr) external payable returns (bytes memory); 89 | function verify(uint256 clusterId) external payable returns (bytes memory); 90 | function verify(bytes32 msgSender, uint256 clusterId) external payable returns (bytes memory); 91 | function remove(bytes32 addr) external payable returns (bytes memory); 92 | function remove(bytes32 msgSender, bytes32 addr) external payable returns (bytes memory); 93 | 94 | function buyName(uint256 msgValue, string memory name) external payable returns (bytes memory); 95 | function buyName(bytes32 msgSender, uint256 msgValue, string memory name) external payable returns (bytes memory); 96 | function fundName(uint256 msgValue, string memory name) external payable returns (bytes memory); 97 | function fundName(bytes32 msgSender, uint256 msgValue, string memory name) 98 | external 99 | payable 100 | returns (bytes memory); 101 | function transferName(string memory name, uint256 toClusterId) external payable returns (bytes memory); 102 | function transferName(bytes32 msgSender, string memory name, uint256 toClusterId) 103 | external 104 | payable 105 | returns (bytes memory); 106 | function pokeName(string memory name) external payable returns (bytes memory); 107 | 108 | function bidName(uint256 msgValue, string memory name) external payable returns (bytes memory); 109 | function bidName(bytes32 msgSender, uint256 msgValue, string memory name) external payable returns (bytes memory); 110 | function reduceBid(string memory name, uint256 amount) external payable returns (bytes memory); 111 | function reduceBid(bytes32 msgSender, string memory name, uint256 amount) external payable returns (bytes memory); 112 | function acceptBid(string memory name) external payable returns (bytes memory); 113 | function acceptBid(bytes32 msgSender, string memory name) external payable returns (bytes memory); 114 | function refundBid() external payable returns (bytes memory); 115 | function refundBid(bytes32 msgSender) external payable returns (bytes memory); 116 | 117 | function setDefaultClusterName(string memory name) external payable returns (bytes memory); 118 | function setDefaultClusterName(bytes32 msgSender, string memory name) external payable returns (bytes memory); 119 | function setWalletName(bytes32 addr, string memory walletname) external payable returns (bytes memory); 120 | function setWalletName(bytes32 msgSender, bytes32 addr, string memory walletname) 121 | external 122 | payable 123 | returns (bytes memory); 124 | } 125 | -------------------------------------------------------------------------------- /src/interfaces/IEndpoint.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | interface IEndpoint { 5 | /// ERRORS /// 6 | 7 | error Invalid(); 8 | error TxFailed(); 9 | error RelayEid(); 10 | error UnknownEid(); 11 | error Unauthorized(); 12 | error Insufficient(); 13 | error MulticallFailed(); 14 | 15 | /// EVENTS /// 16 | 17 | event Nonce(bytes32 indexed addr, uint256 indexed nonce); 18 | event SoftAbort(); 19 | event SignerAddr(address indexed addr); 20 | event ClustersAddr(address indexed addr); 21 | event MessageFailed(uint32 indexed srcEid, uint64 indexed nonce, bytes32 indexed msgSender); 22 | event RefundBalance(bytes32 indexed addr, uint256 indexed amount); 23 | event Refunded(bytes32 indexed msgSender, bytes32 indexed recipient, uint256 indexed amount); 24 | 25 | /// STORAGE /// 26 | 27 | function dstEid() external view returns (uint32); 28 | function clusters() external view returns (address); 29 | function signer() external view returns (address); 30 | function userNonces(bytes32 addr) external view returns (uint256); 31 | function failedTxRefunds(bytes32 addr) external view returns (uint256); 32 | 33 | /// ECDSA HELPERS /// 34 | 35 | function getMulticallHash(bytes[] calldata data) external pure returns (bytes32); 36 | function getOrderHash( 37 | uint256 nonce, 38 | uint256 expirationTimestamp, 39 | uint256 ethAmount, 40 | bytes32 bidder, 41 | string memory name 42 | ) external view returns (bytes32); 43 | function getEthSignedMessageHash(bytes32 messageHash) external pure returns (bytes32); 44 | 45 | function verifyMulticall(bytes[] calldata data, bytes calldata sig) external view returns (bool); 46 | function verifyOrder( 47 | uint256 nonce, 48 | uint256 expirationTimestamp, 49 | uint256 ethAmount, 50 | bytes32 bidder, 51 | string memory name, 52 | bytes calldata sig, 53 | address originator 54 | ) external view returns (bool); 55 | 56 | /// PERMISSIONED FUNCTIONS /// 57 | 58 | function multicall(bytes[] calldata data, bytes calldata sig) external payable returns (bytes[] memory results); 59 | function fulfillOrder( 60 | uint256 msgValue, 61 | uint256 nonce, 62 | uint256 expirationTimestamp, 63 | bytes32 authorized, 64 | string memory name, 65 | bytes calldata sig, 66 | address originator 67 | ) external payable; 68 | function invalidateOrder(uint256 nonce) external payable; 69 | 70 | /// ADMIN FUNCTIONS /// 71 | 72 | function setSignerAddr(address signer_) external; 73 | function setClustersAddr(address clusters_) external; 74 | 75 | /// LAYERZERO /// 76 | 77 | function setDstEid(uint32 eid) external; 78 | function quote(uint32 dstEid, bytes memory message, bytes memory options, bool payInLzToken) 79 | external 80 | returns (uint256 nativeFee, uint256 lzTokenFee); 81 | 82 | function sendPayload(bytes calldata payload) external payable returns (bytes memory result); 83 | function lzSend(bytes memory data, bytes memory options, address refundAddress) 84 | external 85 | payable 86 | returns (bytes memory); 87 | function lzSendMulticall(bytes[] memory data, bytes memory options, address refundAddress) 88 | external 89 | payable 90 | returns (bytes memory); 91 | function gasAirdrop(uint256 msgValue, uint32 dstEid_, bytes memory options) 92 | external 93 | payable 94 | returns (bytes memory); 95 | 96 | function refund() external; 97 | function refund(address recipient) external; 98 | } 99 | -------------------------------------------------------------------------------- /src/interfaces/IPricing.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | interface IPricing { 5 | /// @notice The amount of eth that's been spent on a name since last update 6 | /// @param lastUpdatedPrice Can be greater than max price, used to calculate decay times 7 | /// @param secondsSinceUpdate How many seconds it's been since lastUpdatedPrice 8 | /// @return spent How much eth has been spent 9 | /// @return price The current un-truncated price, which can be greater than maxPrice 10 | function getIntegratedPrice(uint256 lastUpdatedPrice, uint256 secondsSinceUpdate) 11 | external 12 | view 13 | returns (uint256 spent, uint256 price); 14 | 15 | /// @notice The annual price of name registration if nobody bids on the name 16 | /// @return price The annualized price 17 | function minAnnualPrice() external view returns (uint256 price); 18 | } 19 | -------------------------------------------------------------------------------- /src/interfaces/IUUPS.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | interface IUUPS { 5 | function upgradeToAndCall(address newImplementation, bytes memory data) external payable; 6 | } 7 | -------------------------------------------------------------------------------- /test/ClustersBetaCrossChain.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "./Base.t.sol"; 5 | 6 | contract ClustersHubBetaCrossChainTest is Base_Test { 7 | using OptionsBuilder for bytes; 8 | 9 | event Bid(bytes32 from, uint256 amount, bytes32 name); 10 | event Bid(bytes32 from, uint256 amount, bytes32 name, bytes32 referralAddress); 11 | 12 | function setUp() public virtual override { 13 | Base_Test.setUp(); 14 | } 15 | 16 | function testCrosschain() public { 17 | vm.startPrank(users.alicePrimary); 18 | clustersProxy.placeBid{value: 0.1 ether}("foobar"); 19 | 20 | uint256[] memory amounts = new uint256[](2); 21 | amounts[0] = 0.1 ether; 22 | amounts[1] = 0.1 ether; 23 | bytes32[] memory names = new bytes32[](2); 24 | names[0] = "foobar2"; 25 | names[1] = "foobar3"; 26 | clustersProxy.placeBids{value: 0.2 ether}(amounts, names); 27 | 28 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(199_000, 0.1 ether); 29 | bytes memory message = abi.encodeWithSignature("placeBid(bytes32)", bytes32("foobarCrosschain")); 30 | bytes32 from = bytes32(uint256(uint160(address(users.alicePrimary)))); 31 | uint256 nativeFee = initiatorProxy.quote(abi.encode(from, message), options); 32 | 33 | vm.expectEmit(address(clustersProxy)); 34 | emit ClustersHubBeta.Bid(from, 0.1 ether, bytes32("foobarCrosschain")); 35 | 36 | initiatorProxy.lzSend{value: nativeFee}(message, options); 37 | vm.stopPrank(); 38 | } 39 | 40 | function testRemotePlaceBid() public { 41 | vm.startPrank(users.alicePrimary); 42 | bytes memory message = abi.encodeWithSignature("placeBid(bytes32)", _stringToBytes32("foobar")); 43 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(199_000, 0.1 ether); 44 | bytes32 from = _addressToBytes32(users.alicePrimary); 45 | uint256 nativeFee = initiatorProxy.quote(abi.encode(from, message), options); 46 | 47 | vm.expectEmit(address(clustersProxy)); 48 | emit Bid(_addressToBytes32(users.alicePrimary), 0.1 ether, _stringToBytes32("foobar")); 49 | 50 | initiatorProxy.lzSend{value: nativeFee}(message, options); 51 | vm.stopPrank(); 52 | } 53 | 54 | function testRemotePlaceBidWithReferral() public { 55 | vm.startPrank(users.alicePrimary); 56 | bytes memory message = abi.encodeWithSignature( 57 | "placeBid(bytes32,bytes32)", _stringToBytes32("foobar"), _addressToBytes32(users.bobPrimary) 58 | ); 59 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(199_000, 0.1 ether); 60 | bytes32 from = _addressToBytes32(users.alicePrimary); 61 | uint256 nativeFee = initiatorProxy.quote(abi.encode(from, message), options); 62 | 63 | vm.expectEmit(address(clustersProxy)); 64 | emit Bid( 65 | _addressToBytes32(users.alicePrimary), 66 | 0.1 ether, 67 | _stringToBytes32("foobar"), 68 | _addressToBytes32(users.bobPrimary) 69 | ); 70 | 71 | initiatorProxy.lzSend{value: nativeFee}(message, options); 72 | vm.stopPrank(); 73 | } 74 | 75 | function testRemotePlaceBids() public { 76 | vm.startPrank(users.alicePrimary); 77 | uint256[] memory amounts = new uint256[](4); 78 | amounts[0] = 0.1 ether; 79 | amounts[1] = 0.1 ether; 80 | amounts[2] = 0.1 ether; 81 | amounts[3] = 0.1 ether; 82 | bytes32[] memory names = new bytes32[](4); 83 | names[0] = _stringToBytes32("foobar"); 84 | names[1] = _stringToBytes32("ryeshrimp"); 85 | names[2] = _stringToBytes32("munam"); 86 | names[3] = _stringToBytes32("zodomo"); 87 | 88 | bytes memory message = abi.encodeWithSignature("placeBids(uint256[],bytes32[])", amounts, names); 89 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(399_000, 0.4 ether); 90 | bytes32 from = _addressToBytes32(users.alicePrimary); 91 | uint256 nativeFee = initiatorProxy.quote(abi.encode(from, message), options); 92 | 93 | for (uint256 i; i < names.length; ++i) { 94 | vm.expectEmit(address(clustersProxy)); 95 | emit Bid(_addressToBytes32(users.alicePrimary), amounts[i], names[i]); 96 | } 97 | 98 | initiatorProxy.lzSend{value: nativeFee}(message, options); 99 | vm.stopPrank(); 100 | } 101 | 102 | function testRemotePlaceBidsWithReferral() public { 103 | vm.startPrank(users.alicePrimary); 104 | uint256[] memory amounts = new uint256[](4); 105 | amounts[0] = 0.1 ether; 106 | amounts[1] = 0.1 ether; 107 | amounts[2] = 0.1 ether; 108 | amounts[3] = 0.1 ether; 109 | bytes32[] memory names = new bytes32[](4); 110 | names[0] = _stringToBytes32("foobar"); 111 | names[1] = _stringToBytes32("ryeshrimp"); 112 | names[2] = _stringToBytes32("munam"); 113 | names[3] = _stringToBytes32("zodomo"); 114 | 115 | bytes memory message = abi.encodeWithSignature( 116 | "placeBids(uint256[],bytes32[],bytes32)", amounts, names, _addressToBytes32(users.bobPrimary) 117 | ); 118 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(399_000, 0.4 ether); 119 | bytes32 from = _addressToBytes32(users.alicePrimary); 120 | uint256 nativeFee = initiatorProxy.quote(abi.encode(from, message), options); 121 | 122 | for (uint256 i; i < names.length; ++i) { 123 | vm.expectEmit(address(clustersProxy)); 124 | emit Bid(_addressToBytes32(users.alicePrimary), amounts[i], names[i], _addressToBytes32(users.bobPrimary)); 125 | } 126 | 127 | initiatorProxy.lzSend{value: nativeFee}(message, options); 128 | vm.stopPrank(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /test/ClustersBetaSingleChain.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {ClustersHubBeta} from "../src/beta/ClustersHubBeta.sol"; 5 | import {Base_Test} from "./Base.t.sol"; 6 | 7 | contract ClustersHubBetaSingleChainTest is Base_Test { 8 | event Bid(bytes32 from, uint256 amount, bytes32 name); 9 | event Bid(bytes32 from, uint256 amount, bytes32 name, bytes32 referralAddress); 10 | 11 | ClustersHubBeta beta = new ClustersHubBeta(); 12 | 13 | function setUp() public virtual override { 14 | Base_Test.setUp(); 15 | } 16 | 17 | function testBeta() public { 18 | beta.placeBid{value: 0.1 ether}("foobar"); 19 | 20 | uint256[] memory amounts = new uint256[](2); 21 | amounts[0] = 0.1 ether; 22 | amounts[1] = 0.1 ether; 23 | bytes32[] memory names = new bytes32[](2); 24 | names[0] = "foobar2"; 25 | names[1] = "foobar3"; 26 | beta.placeBids{value: 0.2 ether}(amounts, names); 27 | } 28 | 29 | function testLocalPlaceBid() public { 30 | bytes32 from = _addressToBytes32(users.alicePrimary); 31 | vm.expectEmit(address(clustersProxy)); 32 | emit Bid(from, 0.1 ether, _stringToBytes32("foobar")); 33 | vm.prank(users.alicePrimary); 34 | clustersProxy.placeBid{value: 0.1 ether}(_stringToBytes32("foobar")); 35 | } 36 | 37 | function testLocalPlaceBidWithReferral() public { 38 | bytes32 from = _addressToBytes32(users.alicePrimary); 39 | vm.expectEmit(address(clustersProxy)); 40 | emit Bid(from, 0.1 ether, _stringToBytes32("zodomo"), _addressToBytes32(users.bobPrimary)); 41 | vm.prank(users.alicePrimary); 42 | clustersProxy.placeBid{value: 0.1 ether}(_stringToBytes32("zodomo"), _addressToBytes32(users.bobPrimary)); 43 | } 44 | 45 | function testLocalPlaceBids() public { 46 | bytes32 from = _addressToBytes32(users.alicePrimary); 47 | uint256[] memory amounts = new uint256[](4); 48 | amounts[0] = 0.1 ether; 49 | amounts[1] = 0.1 ether; 50 | amounts[2] = 0.1 ether; 51 | amounts[3] = 0.1 ether; 52 | bytes32[] memory names = new bytes32[](4); 53 | names[0] = _stringToBytes32("foobarBatch"); 54 | names[1] = _stringToBytes32("ryeshrimpBatch"); 55 | names[2] = _stringToBytes32("munamBatch"); 56 | names[3] = _stringToBytes32("zodomoBatch"); 57 | 58 | for (uint256 i; i < names.length; ++i) { 59 | vm.expectEmit(address(clustersProxy)); 60 | emit Bid(from, amounts[i], names[i]); 61 | } 62 | vm.prank(users.alicePrimary); 63 | clustersProxy.placeBids{value: 0.4 ether}(amounts, names); 64 | } 65 | 66 | function testLocalPlaceBidsWithReferral() public { 67 | bytes32 from = _addressToBytes32(users.alicePrimary); 68 | uint256[] memory amounts = new uint256[](4); 69 | amounts[0] = 0.1 ether; 70 | amounts[1] = 0.1 ether; 71 | amounts[2] = 0.1 ether; 72 | amounts[3] = 0.1 ether; 73 | bytes32[] memory names = new bytes32[](4); 74 | names[0] = _stringToBytes32("foobarRefer"); 75 | names[1] = _stringToBytes32("ryeshrimpRefer"); 76 | names[2] = _stringToBytes32("munamRefer"); 77 | names[3] = _stringToBytes32("zodomoRefer"); 78 | 79 | for (uint256 i; i < names.length; ++i) { 80 | vm.expectEmit(address(clustersProxy)); 81 | emit Bid(from, amounts[i], names[i], _addressToBytes32(users.bobPrimary)); 82 | } 83 | vm.prank(users.alicePrimary); 84 | clustersProxy.placeBids{value: 0.4 ether}(amounts, names, _addressToBytes32(users.bobPrimary)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /test/EnumerableSetLib.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "forge-std/Test.sol"; 5 | import {EnumerableSetLib} from "clusters/EnumerableSetLib.sol"; 6 | import {LibSort} from "solady/utils/LibSort.sol"; 7 | import {LibPRNG} from "solady/utils/LibPRNG.sol"; 8 | 9 | contract EnumerableSetLibTest is Test { 10 | using EnumerableSetLib for *; 11 | using LibPRNG for *; 12 | 13 | EnumerableSetLib.Bytes32Set s; 14 | 15 | function testEnumerableSetBasic() public { 16 | assertEq(s.length(), 0); 17 | assertEq(s.contains(bytes32(uint256(1))), false); 18 | assertEq(s.contains(bytes32(uint256(2))), false); 19 | assertEq(s.contains(bytes32(uint256(3))), false); 20 | assertEq(s.contains(bytes32(uint256(4))), false); 21 | assertEq(s.contains(bytes32(uint256(5))), false); 22 | 23 | assertTrue(s.add(bytes32(uint256(1)))); 24 | assertFalse(s.add(bytes32(uint256(1)))); 25 | 26 | assertEq(s.length(), 1); 27 | assertEq(s.contains(bytes32(uint256(1))), true); 28 | assertEq(s.contains(bytes32(uint256(2))), false); 29 | assertEq(s.contains(bytes32(uint256(3))), false); 30 | assertEq(s.contains(bytes32(uint256(4))), false); 31 | assertEq(s.contains(bytes32(uint256(5))), false); 32 | 33 | assertTrue(s.add(bytes32(uint256(2)))); 34 | assertFalse(s.add(bytes32(uint256(2)))); 35 | 36 | assertEq(s.length(), 2); 37 | assertEq(s.contains(bytes32(uint256(1))), true); 38 | assertEq(s.contains(bytes32(uint256(2))), true); 39 | assertEq(s.contains(bytes32(uint256(3))), false); 40 | assertEq(s.contains(bytes32(uint256(4))), false); 41 | assertEq(s.contains(bytes32(uint256(5))), false); 42 | 43 | assertTrue(s.add(bytes32(uint256(3)))); 44 | assertFalse(s.add(bytes32(uint256(3)))); 45 | 46 | assertEq(s.length(), 3); 47 | assertEq(s.contains(bytes32(uint256(1))), true); 48 | assertEq(s.contains(bytes32(uint256(2))), true); 49 | assertEq(s.contains(bytes32(uint256(3))), true); 50 | assertEq(s.contains(bytes32(uint256(4))), false); 51 | assertEq(s.contains(bytes32(uint256(5))), false); 52 | 53 | assertTrue(s.add(bytes32(uint256(4)))); 54 | assertFalse(s.add(bytes32(uint256(4)))); 55 | 56 | assertEq(s.length(), 4); 57 | assertEq(s.contains(bytes32(uint256(1))), true); 58 | assertEq(s.contains(bytes32(uint256(2))), true); 59 | assertEq(s.contains(bytes32(uint256(3))), true); 60 | assertEq(s.contains(bytes32(uint256(4))), true); 61 | assertEq(s.contains(bytes32(uint256(5))), false); 62 | 63 | assertTrue(s.add(bytes32(uint256(5)))); 64 | assertFalse(s.add(bytes32(uint256(5)))); 65 | 66 | assertEq(s.length(), 5); 67 | assertEq(s.contains(bytes32(uint256(1))), true); 68 | assertEq(s.contains(bytes32(uint256(2))), true); 69 | assertEq(s.contains(bytes32(uint256(3))), true); 70 | assertEq(s.contains(bytes32(uint256(4))), true); 71 | assertEq(s.contains(bytes32(uint256(5))), true); 72 | } 73 | 74 | function testEnumerableSetBasic2() public { 75 | s.add(bytes32(uint256(1))); 76 | s.add(bytes32(uint256(2))); 77 | 78 | s.remove(bytes32(uint256(1))); 79 | assertEq(s.length(), 1); 80 | s.remove(bytes32(uint256(2))); 81 | assertEq(s.length(), 0); 82 | 83 | s.add(bytes32(uint256(1))); 84 | s.add(bytes32(uint256(2))); 85 | 86 | s.remove(bytes32(uint256(2))); 87 | assertEq(s.length(), 1); 88 | s.remove(bytes32(uint256(1))); 89 | assertEq(s.length(), 0); 90 | 91 | s.add(bytes32(uint256(1))); 92 | s.add(bytes32(uint256(2))); 93 | s.add(bytes32(uint256(3))); 94 | 95 | s.remove(bytes32(uint256(3))); 96 | assertEq(s.length(), 2); 97 | s.remove(bytes32(uint256(2))); 98 | assertEq(s.length(), 1); 99 | s.remove(bytes32(uint256(1))); 100 | assertEq(s.length(), 0); 101 | 102 | s.add(bytes32(uint256(1))); 103 | s.add(bytes32(uint256(2))); 104 | s.add(bytes32(uint256(3))); 105 | 106 | s.remove(bytes32(uint256(1))); 107 | assertEq(s.length(), 2); 108 | s.remove(bytes32(uint256(2))); 109 | assertEq(s.length(), 1); 110 | s.remove(bytes32(uint256(3))); 111 | assertEq(s.length(), 0); 112 | } 113 | 114 | function testEnumerableSetFuzz(uint256 n) public { 115 | unchecked { 116 | LibPRNG.PRNG memory prng; 117 | prng.state = n; 118 | uint256[] memory additions = new uint256[](prng.next() % 16); 119 | 120 | for (uint256 i; i != additions.length; ++i) { 121 | uint256 x = 1 | (prng.next() & 7); 122 | additions[i] = x; 123 | s.add(bytes32(x)); 124 | assertTrue(s.contains(bytes32(x))); 125 | } 126 | LibSort.sort(additions); 127 | LibSort.uniquifySorted(additions); 128 | assertEq(s.length(), additions.length); 129 | { 130 | bytes32[] memory values = s.values(); 131 | uint256[] memory valuesCasted = _toUints(values); 132 | LibSort.sort(valuesCasted); 133 | assertEq(valuesCasted, additions); 134 | } 135 | 136 | uint256[] memory removals = new uint256[](prng.next() % 16); 137 | for (uint256 i; i != removals.length; ++i) { 138 | uint256 x = 1 | (prng.next() & 7); 139 | removals[i] = x; 140 | s.remove(bytes32(x)); 141 | assertFalse(s.contains(bytes32(x))); 142 | } 143 | LibSort.sort(removals); 144 | LibSort.uniquifySorted(removals); 145 | 146 | { 147 | uint256[] memory difference = LibSort.difference(additions, removals); 148 | bytes32[] memory values = s.values(); 149 | uint256[] memory valuesCasted = _toUints(values); 150 | LibSort.sort(valuesCasted); 151 | assertEq(valuesCasted, difference); 152 | } 153 | } 154 | } 155 | 156 | function _toUints(bytes32[] memory a) private pure returns (uint256[] memory result) { 157 | /// @solidity memory-safe-assembly 158 | assembly { 159 | result := a 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /test/GasBenchmark.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "./Base.t.sol"; 5 | 6 | contract GasBenchmarkTest is Base_Test { 7 | using OptionsBuilder for bytes; 8 | using EnumerableSet for EnumerableSet.AddressSet; 9 | 10 | event Bid(bytes32 from, uint256 amount, bytes32 name); 11 | event Bid(bytes32 from, uint256 amount, bytes32 name, bytes32 referralAddress); 12 | 13 | function setUp() public virtual override { 14 | Base_Test.setUp(); 15 | configureHarbergerEnvironment(); 16 | } 17 | 18 | function testBenchmark() public { 19 | //// Full contract testing 20 | bytes[] memory buyBatchData = new bytes[](2); 21 | buyBatchData[0] = abi.encodeWithSignature( 22 | "buyName(bytes32,uint256,string)", users.alicePrimary, minPrice, constants.TEST_NAME() 23 | ); 24 | buyBatchData[1] = 25 | abi.encodeWithSignature("buyName(bytes32,uint256,string)", users.alicePrimary, minPrice, "zodomo"); 26 | 27 | vm.startPrank(users.signer); 28 | bytes32 messageHash = endpointProxy.getMulticallHash(buyBatchData); 29 | bytes32 digest = endpointProxy.getEthSignedMessageHash(messageHash); 30 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(users.signerPrivKey, digest); 31 | bytes memory sig1 = abi.encodePacked(r, s, v); 32 | vm.stopPrank(); 33 | 34 | vm.startPrank(users.alicePrimary); 35 | IEndpoint(endpointGroup.at(0)).multicall{value: 2 * minPrice}(buyBatchData, sig1); 36 | clusters.fundName{value: 0.5 ether}(0.5 ether, constants.TEST_NAME()); 37 | clusters.add(_addressToBytes32(users.aliceSecondary)); 38 | clusters.setDefaultClusterName("zodomo"); 39 | clusters.setWalletName(_addressToBytes32(users.alicePrimary), "Primary"); 40 | vm.stopPrank(); 41 | 42 | vm.prank(users.aliceSecondary); 43 | clusters.verify(1); 44 | 45 | vm.startPrank(users.alicePrimary); 46 | bytes[] memory data = new bytes[](5); 47 | data[0] = abi.encodeWithSignature("fundName(uint256,string)", 0.5 ether, constants.TEST_NAME()); 48 | data[1] = abi.encodeWithSignature("fundName(uint256,string)", 1 ether, "zodomo"); 49 | data[2] = abi.encodeWithSignature("setDefaultClusterName(string)", constants.TEST_NAME()); 50 | data[3] = 51 | abi.encodeWithSignature("setWalletName(bytes32,string)", _addressToBytes32(users.alicePrimary), "Main"); 52 | data[4] = abi.encodeWithSignature( 53 | "setWalletName(bytes32,string)", _addressToBytes32(users.aliceSecondary), "Secondary" 54 | ); 55 | clusters.multicall{value: minPrice + 1.5 ether}(data); 56 | clusters.remove(_addressToBytes32(users.aliceSecondary)); 57 | vm.stopPrank(); 58 | 59 | vm.startPrank(users.bidder); 60 | clusters.bidName{value: 2 ether}(2 ether, constants.TEST_NAME()); 61 | vm.warp(constants.START_TIME() + 14 days); 62 | clusters.pokeName(constants.TEST_NAME()); 63 | vm.warp(constants.START_TIME() + 31 days); 64 | clusters.reduceBid(constants.TEST_NAME(), 1 ether); 65 | vm.stopPrank(); 66 | 67 | vm.startPrank(users.alicePrimary); 68 | clusters.buyName{value: minPrice}(minPrice, "burned"); 69 | clusters.transferName("burned", 0); 70 | clusters.acceptBid(constants.TEST_NAME()); 71 | clusters.transferName("zodomo", 2); 72 | vm.stopPrank(); 73 | 74 | //// Beta contract testing 75 | bytes memory message; 76 | bytes memory options; 77 | bytes32 from = _addressToBytes32(users.alicePrimary); 78 | uint256 nativeFee; 79 | uint256[] memory amounts; 80 | bytes32[] memory names; 81 | vm.startPrank(users.alicePrimary); 82 | 83 | /// Local 84 | // placeBid 85 | vm.expectEmit(address(clustersProxy)); 86 | emit Bid(from, 0.1 ether, _stringToBytes32("foobarLocal")); 87 | clustersProxy.placeBid{value: 0.1 ether}(_stringToBytes32("foobarLocal")); 88 | 89 | // placeBid with referral 90 | vm.expectEmit(address(clustersProxy)); 91 | emit Bid(from, 0.1 ether, _stringToBytes32("zodomoLocal"), _addressToBytes32(users.bobPrimary)); 92 | clustersProxy.placeBid{value: 0.1 ether}(_stringToBytes32("zodomoLocal"), _addressToBytes32(users.bobPrimary)); 93 | 94 | // placeBids 95 | amounts = new uint256[](4); 96 | amounts[0] = 0.1 ether; 97 | amounts[1] = 0.1 ether; 98 | amounts[2] = 0.1 ether; 99 | amounts[3] = 0.1 ether; 100 | names = new bytes32[](4); 101 | names[0] = _stringToBytes32("foobarLocalBatch"); 102 | names[1] = _stringToBytes32("ryeshrimpLocalBatch"); 103 | names[2] = _stringToBytes32("munamLocalBatch"); 104 | names[3] = _stringToBytes32("zodomoLocalBatch"); 105 | 106 | for (uint256 i; i < names.length; ++i) { 107 | vm.expectEmit(address(clustersProxy)); 108 | emit Bid(from, amounts[i], names[i]); 109 | } 110 | clustersProxy.placeBids{value: 0.4 ether}(amounts, names); 111 | 112 | // placeBids with referral 113 | amounts = new uint256[](4); 114 | amounts[0] = 0.1 ether; 115 | amounts[1] = 0.1 ether; 116 | amounts[2] = 0.1 ether; 117 | amounts[3] = 0.1 ether; 118 | names = new bytes32[](4); 119 | names[0] = _stringToBytes32("foobarLocalRefer"); 120 | names[1] = _stringToBytes32("ryeshrimpLocalRefer"); 121 | names[2] = _stringToBytes32("munamLocalRefer"); 122 | names[3] = _stringToBytes32("zodomoLocalRefer"); 123 | 124 | for (uint256 i; i < names.length; ++i) { 125 | vm.expectEmit(address(clustersProxy)); 126 | emit Bid(from, amounts[i], names[i], _addressToBytes32(users.bobPrimary)); 127 | } 128 | clustersProxy.placeBids{value: 0.4 ether}(amounts, names, _addressToBytes32(users.bobPrimary)); 129 | 130 | /// Remote 131 | // placeBid 132 | message = abi.encodeWithSignature("placeBid(bytes32)", _stringToBytes32("foobarRemote")); 133 | options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(199_000, 0.1 ether); 134 | nativeFee = initiatorProxy.quote(abi.encode(from, message), options); 135 | 136 | vm.expectEmit(address(clustersProxy)); 137 | emit Bid(from, 0.1 ether, _stringToBytes32("foobarRemote")); 138 | initiatorProxy.lzSend{value: nativeFee}(message, options); 139 | 140 | // placeBid with referral 141 | message = abi.encodeWithSignature( 142 | "placeBid(bytes32,bytes32)", _stringToBytes32("zodomoRemote"), _addressToBytes32(users.bobPrimary) 143 | ); 144 | options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(199_000, 0.1 ether); 145 | nativeFee = initiatorProxy.quote(abi.encode(from, message), options); 146 | 147 | vm.expectEmit(address(clustersProxy)); 148 | emit Bid(from, 0.1 ether, _stringToBytes32("zodomoRemote"), _addressToBytes32(users.bobPrimary)); 149 | initiatorProxy.lzSend{value: nativeFee}(message, options); 150 | 151 | // placeBids 152 | amounts = new uint256[](4); 153 | amounts[0] = 0.1 ether; 154 | amounts[1] = 0.1 ether; 155 | amounts[2] = 0.1 ether; 156 | amounts[3] = 0.1 ether; 157 | names = new bytes32[](4); 158 | names[0] = _stringToBytes32("foobarRemoteBatch"); 159 | names[1] = _stringToBytes32("ryeshrimpRemoteBatch"); 160 | names[2] = _stringToBytes32("munamRemoteBatch"); 161 | names[3] = _stringToBytes32("zodomoRemoteBatch"); 162 | 163 | message = abi.encodeWithSignature("placeBids(uint256[],bytes32[])", amounts, names); 164 | options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(399_000, 0.4 ether); 165 | nativeFee = initiatorProxy.quote(abi.encode(from, message), options); 166 | 167 | for (uint256 i; i < names.length; ++i) { 168 | vm.expectEmit(address(clustersProxy)); 169 | emit Bid(from, amounts[i], names[i]); 170 | } 171 | initiatorProxy.lzSend{value: nativeFee}(message, options); 172 | 173 | // placeBids with referral 174 | amounts = new uint256[](4); 175 | amounts[0] = 0.1 ether; 176 | amounts[1] = 0.1 ether; 177 | amounts[2] = 0.1 ether; 178 | amounts[3] = 0.1 ether; 179 | names = new bytes32[](4); 180 | names[0] = _stringToBytes32("foobarRemoteRefer"); 181 | names[1] = _stringToBytes32("ryeshrimpRemoteRefer"); 182 | names[2] = _stringToBytes32("munamRemoteRefer"); 183 | names[3] = _stringToBytes32("zodomoRemoteRefer"); 184 | 185 | message = abi.encodeWithSignature( 186 | "placeBids(uint256[],bytes32[],bytes32)", amounts, names, _addressToBytes32(users.bobPrimary) 187 | ); 188 | options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(399_000, 0.4 ether); 189 | nativeFee = initiatorProxy.quote(abi.encode(from, message), options); 190 | 191 | for (uint256 i; i < names.length; ++i) { 192 | vm.expectEmit(address(clustersProxy)); 193 | emit Bid(from, amounts[i], names[i], _addressToBytes32(users.bobPrimary)); 194 | } 195 | initiatorProxy.lzSend{value: nativeFee}(message, options); 196 | 197 | vm.stopPrank(); 198 | 199 | vm.prank(0x000000dE1E80ea5a234FB5488fee2584251BC7e8); 200 | clustersProxy.withdraw(payable(0x000000dE1E80ea5a234FB5488fee2584251BC7e8), 2 ether); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /test/harness/PricingHarbergerHarness.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger} from "clusters/PricingHarberger.sol"; 5 | 6 | contract PricingHarbergerHarness is PricingHarberger { 7 | /// EXPOSED INTERNAL FUNCTIONS /// 8 | 9 | function exposed_getMaxIntersection(uint256 p, uint256 yearsSinceDeploymentWad) public pure returns (uint256) { 10 | return getMaxIntersection(p, yearsSinceDeploymentWad); 11 | } 12 | 13 | function exposed_getMinIntersection(uint256 p) public pure returns (uint256) { 14 | return getMinIntersection(p); 15 | } 16 | 17 | function exposed_getIntegratedMaxPrice(uint256 numSeconds) public pure returns (uint256) { 18 | return getIntegratedMaxPrice(numSeconds); 19 | } 20 | 21 | function exposed_getMaxPrice(uint256 numSeconds) public pure returns (uint256) { 22 | return getMaxPrice(numSeconds); 23 | } 24 | 25 | function exposed_getIntegratedDecayPrice(uint256 p0, uint256 numSeconds) public pure returns (uint256) { 26 | return getIntegratedDecayPrice(p0, numSeconds); 27 | } 28 | 29 | function exposed_getDecayPrice(uint256 p0, uint256 numSeconds) public pure returns (uint256) { 30 | return getDecayPrice(p0, numSeconds); 31 | } 32 | 33 | function exposed_getDecayMultiplier(uint256 numSeconds) public pure returns (uint256) { 34 | return getDecayMultiplier(numSeconds); 35 | } 36 | 37 | function exposed_getPriceAfterBid(uint256 p0, uint256 pBid, uint256 bidLengthInSeconds) 38 | public 39 | pure 40 | returns (uint256) 41 | { 42 | return getPriceAfterBid(p0, pBid, bidLengthInSeconds); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/mocks/FickleReceiver.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | contract FickleReceiver { 5 | error NotReceiving(); 6 | 7 | bool isReceiving = true; 8 | 9 | constructor() {} 10 | 11 | function toggle() external { 12 | isReceiving = !isReceiving; 13 | } 14 | 15 | function execute(address to, uint256 value, bytes memory data) 16 | external 17 | returns (bool success, bytes memory result) 18 | { 19 | (success, result) = to.call{value: value}(data); 20 | if (!success) { 21 | if (result.length > 0) { 22 | assembly { 23 | let result_size := mload(result) 24 | revert(add(32, result), result_size) 25 | } 26 | } else { 27 | revert("External call failed without revert reason"); 28 | } 29 | } 30 | } 31 | 32 | receive() external payable { 33 | if (!isReceiving) revert NotReceiving(); 34 | } 35 | 36 | fallback() external payable { 37 | if (!isReceiving) revert NotReceiving(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_acceptBid.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_acceptBid_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | vm.stopPrank(); 14 | 15 | vm.startPrank(users.bobPrimary); 16 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 17 | vm.stopPrank(); 18 | 19 | vm.startPrank(users.bidder); 20 | clusters.bidName{value: minPrice * 2}(minPrice * 2, constants.TEST_NAME()); 21 | vm.stopPrank(); 22 | 23 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 10 days); 24 | } 25 | 26 | function testAcceptBid() public { 27 | vm.startPrank(users.alicePrimary); 28 | clusters.acceptBid(constants.TEST_NAME()); 29 | vm.stopPrank(); 30 | 31 | bytes32[] memory empty; 32 | bytes32[] memory verified = new bytes32[](1); 33 | verified[0] = _addressToBytes32(users.bidder); 34 | bytes32[] memory names = new bytes32[](1); 35 | names[0] = _stringToBytes32(constants.TEST_NAME()); 36 | uint256 protocolAccrual = clusters.protocolAccrual(); 37 | uint256 totalNameBacking = clusters.totalNameBacking(); 38 | assertEq(minPrice * 2, protocolAccrual + totalNameBacking, "protocolAccrual and totalNameBacking incoherence"); 39 | assertBalances(minPrice * 2, protocolAccrual, totalNameBacking, 0); 40 | assertUnverifiedAddresses(3, 0, empty); 41 | assertVerifiedAddresses(3, 1, verified); 42 | assertClusterNames(3, 1, names); 43 | } 44 | 45 | function testAcceptBid_Reverts() public { 46 | string memory testName = constants.TEST_NAME(); 47 | vm.startPrank(users.hacker); 48 | 49 | vm.expectRevert(IClustersHub.EmptyName.selector); 50 | clusters.acceptBid(""); 51 | vm.expectRevert(IClustersHub.LongName.selector); 52 | clusters.acceptBid("Privacy is necessary for an open society in the electronic age."); 53 | 54 | vm.expectRevert(IClustersHub.NoCluster.selector); 55 | clusters.acceptBid(testName); 56 | vm.stopPrank(); 57 | 58 | vm.startPrank(users.bobPrimary); 59 | vm.expectRevert(IClustersHub.Unauthorized.selector); 60 | clusters.acceptBid(testName); 61 | vm.expectRevert(IClustersHub.Unauthorized.selector); 62 | clusters.acceptBid(_addressToBytes32(users.alicePrimary), testName); 63 | 64 | vm.expectRevert(IClustersHub.NoBid.selector); 65 | clusters.acceptBid("zodomo"); 66 | vm.stopPrank(); 67 | 68 | bytes memory data = abi.encodeWithSignature("buyName(uint256,string)", minPrice, "FOOBAR"); 69 | fickleReceiver.execute(address(clusters), minPrice, data); 70 | fickleReceiver.toggle(); 71 | 72 | vm.prank(users.bidder); 73 | clusters.bidName{value: minPrice * 3}(minPrice * 3, "FOOBAR"); 74 | 75 | data = abi.encodeWithSignature("acceptBid(string)", "FOOBAR"); 76 | vm.expectRevert(IClustersHub.NativeTokenTransferFailed.selector); 77 | fickleReceiver.execute(address(clusters), 0, data); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_add.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_add_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | vm.stopPrank(); 14 | vm.prank(users.bobPrimary); 15 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 16 | } 17 | 18 | function testAdd() public { 19 | vm.startPrank(users.alicePrimary); 20 | clusters.add(_addressToBytes32(users.aliceSecondary)); 21 | clusters.add(_addressToBytes32(users.bobPrimary)); 22 | 23 | bytes32[] memory unverified = new bytes32[](1); 24 | bytes32[] memory verified = new bytes32[](1); 25 | unverified[0] = _addressToBytes32(users.aliceSecondary); 26 | verified[0] = _addressToBytes32(users.alicePrimary); 27 | assertUnverifiedAddresses(1, 2, unverified); 28 | assertVerifiedAddresses(1, 1, verified); 29 | verified[0] = _addressToBytes32(users.bobPrimary); 30 | assertVerifiedAddresses(2, 1, verified); 31 | } 32 | 33 | function testAdd_Reverts() public { 34 | vm.startPrank(users.hacker); 35 | vm.expectRevert(IClustersHub.NoCluster.selector); 36 | clusters.add(_addressToBytes32(users.alicePrimary)); 37 | vm.expectRevert(IClustersHub.Unauthorized.selector); 38 | clusters.add(_addressToBytes32(users.alicePrimary), _addressToBytes32(users.alicePrimary)); 39 | vm.stopPrank(); 40 | 41 | vm.prank(users.alicePrimary); 42 | vm.expectRevert(IClustersHub.Registered.selector); 43 | clusters.add(_addressToBytes32(users.alicePrimary)); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_bidName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_bidName_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | vm.stopPrank(); 14 | 15 | vm.startPrank(users.bobPrimary); 16 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 17 | vm.stopPrank(); 18 | } 19 | 20 | function testBidName() public { 21 | vm.startPrank(users.bobPrimary); 22 | clusters.bidName{value: minPrice}(minPrice, constants.TEST_NAME()); 23 | vm.stopPrank(); 24 | 25 | (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder) = 26 | clusters.bids(_stringToBytes32(constants.TEST_NAME())); 27 | assertEq(ethAmount, minPrice, "bid value error"); 28 | assertEq(createdTimestamp, constants.MARKET_OPEN_TIMESTAMP() + 1 days, "bid timestamp error"); 29 | assertEq(bidder, _addressToBytes32(users.bobPrimary), "bid bidder error"); 30 | assertBalances(minPrice * 3, 0, minPrice * 2, minPrice); 31 | } 32 | 33 | function testBidNameIncrease() public { 34 | vm.startPrank(users.bobPrimary); 35 | clusters.bidName{value: minPrice}(minPrice, constants.TEST_NAME()); 36 | clusters.bidName{value: minPrice}(minPrice, constants.TEST_NAME()); 37 | vm.stopPrank(); 38 | 39 | (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder) = 40 | clusters.bids(_stringToBytes32(constants.TEST_NAME())); 41 | assertEq(ethAmount, minPrice * 2, "bid value error"); 42 | assertEq(createdTimestamp, constants.MARKET_OPEN_TIMESTAMP() + 1 days, "bid timestamp error"); 43 | assertEq(bidder, _addressToBytes32(users.bobPrimary), "bid bidder error"); 44 | assertBalances(minPrice * 4, 0, minPrice * 2, minPrice * 2); 45 | } 46 | 47 | function testBidNameOutbid() public { 48 | string memory testName = constants.TEST_NAME(); 49 | vm.startPrank(users.bobPrimary); 50 | clusters.bidName{value: minPrice}(minPrice, testName); 51 | vm.stopPrank(); 52 | 53 | vm.prank(users.bidder); 54 | clusters.bidName{value: minPrice * 2}(minPrice * 2, testName); 55 | 56 | (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder) = 57 | clusters.bids(_stringToBytes32(constants.TEST_NAME())); 58 | assertEq(ethAmount, minPrice * 2, "bid value not updated"); 59 | assertEq(createdTimestamp, constants.MARKET_OPEN_TIMESTAMP() + 1 days, "bid timestamp error"); 60 | assertEq(bidder, _addressToBytes32(users.bidder), "bid bidder not updated"); 61 | assertBalances(minPrice * 4, 0, minPrice * 2, minPrice * 2); 62 | } 63 | 64 | function testBidNameOutbidFickleReceiver() public { 65 | string memory testName = constants.TEST_NAME(); 66 | bytes memory data = abi.encodeWithSignature("bidName(uint256,string)", minPrice, testName); 67 | fickleReceiver.execute(address(clusters), minPrice, data); 68 | fickleReceiver.toggle(); 69 | 70 | vm.prank(users.bidder); 71 | clusters.bidName{value: minPrice * 2}(minPrice * 2, testName); 72 | 73 | (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder) = 74 | clusters.bids(_stringToBytes32(constants.TEST_NAME())); 75 | assertEq(ethAmount, minPrice * 2, "bid value not updated"); 76 | assertEq(createdTimestamp, constants.MARKET_OPEN_TIMESTAMP() + 1 days, "bid timestamp error"); 77 | assertEq(bidder, _addressToBytes32(users.bidder), "bid bidder not updated"); 78 | assertBalances(minPrice * 5, 0, minPrice * 2, minPrice * 3); 79 | } 80 | 81 | function testBidName_Reverts() public { 82 | string memory testName = constants.TEST_NAME(); 83 | vm.startPrank(users.alicePrimary); 84 | vm.expectRevert(IClustersHub.EmptyName.selector); 85 | clusters.bidName{value: minPrice}(minPrice, ""); 86 | vm.expectRevert(IClustersHub.LongName.selector); 87 | clusters.bidName{value: minPrice}(minPrice, "Privacy is necessary for an open society in the electronic age."); 88 | 89 | vm.expectRevert(IClustersHub.NoBid.selector); 90 | clusters.bidName{value: 0}(0, "zodomo"); 91 | vm.expectRevert(IClustersHub.SelfBid.selector); 92 | clusters.bidName{value: minPrice}(minPrice, testName); 93 | 94 | vm.expectRevert(IClustersHub.Unregistered.selector); 95 | clusters.bidName{value: minPrice}(minPrice, "FOOBAR"); 96 | vm.expectRevert(IClustersHub.Insufficient.selector); 97 | clusters.bidName{value: minPrice - 1}(minPrice - 1, "zodomo"); 98 | 99 | vm.expectRevert(IClustersHub.BadInvariant.selector); 100 | clusters.bidName{value: minPrice}(minPrice + 1, "zodomo"); 101 | clusters.bidName{value: minPrice * 2}(minPrice * 2, "zodomo"); 102 | vm.stopPrank(); 103 | 104 | vm.prank(users.bidder); 105 | vm.expectRevert(IClustersHub.Insufficient.selector); 106 | clusters.bidName{value: minPrice}(minPrice, "zodomo"); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_buyName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_buyName_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function testBuyName() public { 9 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 10 | vm.startPrank(users.alicePrimary); 11 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 12 | vm.stopPrank(); 13 | 14 | bytes32[] memory unverified; 15 | bytes32[] memory verified = new bytes32[](1); 16 | verified[0] = _addressToBytes32(users.alicePrimary); 17 | bytes32[] memory names = new bytes32[](1); 18 | names[0] = _stringToBytes32(constants.TEST_NAME()); 19 | assertBalances(minPrice, 0, minPrice, 0); 20 | assertUnverifiedAddresses(1, 0, unverified); 21 | assertVerifiedAddresses(1, 1, verified); 22 | assertClusterNames(1, 1, names); 23 | } 24 | 25 | function testBuyNameExtra() public { 26 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 27 | vm.startPrank(users.alicePrimary); 28 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 29 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 30 | vm.stopPrank(); 31 | 32 | bytes32[] memory unverified; 33 | bytes32[] memory verified = new bytes32[](1); 34 | verified[0] = _addressToBytes32(users.alicePrimary); 35 | bytes32[] memory names = new bytes32[](1); 36 | names[0] = _stringToBytes32(constants.TEST_NAME()); 37 | names[0] = _stringToBytes32("zodomo"); 38 | assertBalances(minPrice * 2, 0, minPrice * 2, 0); 39 | assertUnverifiedAddresses(1, 0, unverified); 40 | assertVerifiedAddresses(1, 1, verified); 41 | assertClusterNames(1, 2, names); 42 | } 43 | 44 | function testBuyName_Reverts() public { 45 | string memory testName = constants.TEST_NAME(); 46 | vm.startPrank(users.alicePrimary); 47 | vm.expectRevert(IClustersHub.Unauthorized.selector); 48 | clusters.buyName{value: minPrice}(minPrice, testName); 49 | vm.expectRevert(IClustersHub.Unauthorized.selector); 50 | clusters.buyName{value: minPrice}(_addressToBytes32(users.bobPrimary), minPrice, testName); 51 | 52 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 53 | 54 | vm.expectRevert(IClustersHub.EmptyName.selector); 55 | clusters.buyName{value: minPrice}(minPrice, ""); 56 | vm.expectRevert(IClustersHub.LongName.selector); 57 | clusters.buyName{value: minPrice}(minPrice, "Privacy is necessary for an open society in the electronic age."); 58 | 59 | vm.expectRevert(IClustersHub.Insufficient.selector); 60 | clusters.buyName{value: minPrice - 1}(minPrice - 1, testName); 61 | vm.expectRevert(IClustersHub.BadInvariant.selector); 62 | clusters.buyName{value: minPrice}(minPrice + 1, testName); 63 | 64 | clusters.buyName{value: minPrice}(minPrice, testName); 65 | vm.expectRevert(IClustersHub.Registered.selector); 66 | clusters.buyName{value: minPrice}(minPrice, testName); 67 | vm.stopPrank(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_fundName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_fundName_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | vm.stopPrank(); 14 | } 15 | 16 | function testFundName() public { 17 | vm.startPrank(users.alicePrimary); 18 | clusters.fundName{value: minPrice}(minPrice, constants.TEST_NAME()); 19 | vm.stopPrank(); 20 | 21 | assertNameBacking(constants.TEST_NAME(), minPrice * 2); 22 | } 23 | 24 | function testFundNameOther() public { 25 | vm.startPrank(users.bobPrimary); 26 | clusters.fundName{value: minPrice}(minPrice, constants.TEST_NAME()); 27 | vm.stopPrank(); 28 | 29 | assertNameBacking(constants.TEST_NAME(), minPrice * 2); 30 | } 31 | 32 | function testFundName_Reverts() public { 33 | string memory testName = constants.TEST_NAME(); 34 | vm.startPrank(users.alicePrimary); 35 | vm.expectRevert(IClustersHub.EmptyName.selector); 36 | clusters.fundName{value: minPrice}(minPrice, ""); 37 | vm.expectRevert(IClustersHub.LongName.selector); 38 | clusters.fundName{value: minPrice}(minPrice, "Privacy is necessary for an open society in the electronic age."); 39 | 40 | vm.expectRevert(IClustersHub.Unregistered.selector); 41 | clusters.fundName{value: minPrice}(minPrice, "zodomo"); 42 | vm.expectRevert(IClustersHub.Unauthorized.selector); 43 | clusters.fundName{value: minPrice}(_addressToBytes32(users.bobPrimary), minPrice, "zodomo"); 44 | 45 | vm.expectRevert(IClustersHub.BadInvariant.selector); 46 | clusters.fundName{value: minPrice}(minPrice + 1, testName); 47 | vm.stopPrank(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_multicall.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_multicall_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | } 12 | 13 | function testMulticall_Reverts() public { 14 | bytes[] memory data = new bytes[](1); 15 | data[0] = abi.encodeWithSignature("verify(uint256)", 1); 16 | vm.prank(users.alicePrimary); 17 | vm.expectRevert(IClustersHub.MulticallFailed.selector); 18 | clusters.multicall(data); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_pokeName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_pokeName_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 14 | vm.stopPrank(); 15 | 16 | vm.startPrank(users.bobPrimary); 17 | clusters.bidName{value: minPrice}(minPrice, "zodomo"); 18 | vm.stopPrank(); 19 | 20 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 31 days); 21 | } 22 | 23 | function testPokeName() public { 24 | clusters.pokeName(constants.TEST_NAME()); 25 | clusters.pokeName("zodomo"); 26 | 27 | bytes32[] memory names = clusters.getClusterNamesBytes32(1); 28 | uint256 protocolAccrual = clusters.protocolAccrual(); 29 | uint256 totalNameBacking = clusters.totalNameBacking(); 30 | assertEq(protocolAccrual > 0, true, "protocolAccrual didn't increase"); 31 | assertEq(totalNameBacking < minPrice * 2, true, "totalNameBacking didn't decrease"); 32 | assertEq(minPrice * 2, protocolAccrual + totalNameBacking, "protocolAccrual and totalNameBacking incohesive"); 33 | assertBalances(minPrice * 3, protocolAccrual, totalNameBacking, minPrice); 34 | assertClusterNames(1, 2, names); 35 | } 36 | 37 | function testPokeNameExhaustBacking() public { 38 | clusters.pokeName(constants.TEST_NAME()); 39 | clusters.pokeName("zodomo"); 40 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + (10 * 365 days)); 41 | clusters.pokeName(constants.TEST_NAME()); 42 | clusters.pokeName("zodomo"); 43 | 44 | bytes32[] memory empty; 45 | bytes32[] memory addrs = new bytes32[](1); 46 | addrs[0] = _addressToBytes32(users.alicePrimary); 47 | bytes32[] memory names = clusters.getClusterNamesBytes32(2); 48 | assertBalances(minPrice * 3, minPrice * 2, minPrice, 0); 49 | assertVerifiedAddresses(1, 1, addrs); 50 | addrs = clusters.getVerifiedAddresses(2); 51 | assertVerifiedAddresses(2, 1, addrs); 52 | assertClusterNames(1, 0, empty); 53 | assertClusterNames(2, 1, names); 54 | } 55 | 56 | function testPokeName_Reverts() public { 57 | vm.startPrank(users.alicePrimary); 58 | vm.expectRevert(IClustersHub.EmptyName.selector); 59 | clusters.pokeName(""); 60 | vm.expectRevert(IClustersHub.LongName.selector); 61 | clusters.pokeName("Privacy is necessary for an open society in the electronic age."); 62 | 63 | vm.expectRevert(IClustersHub.Unregistered.selector); 64 | clusters.pokeName("FOOBAR"); 65 | vm.stopPrank(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_reduceBid.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_reduceBid_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | vm.stopPrank(); 14 | 15 | vm.startPrank(users.bobPrimary); 16 | clusters.bidName{value: minPrice * 2}(minPrice * 2, constants.TEST_NAME()); 17 | vm.stopPrank(); 18 | 19 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 32 days); 20 | } 21 | 22 | function testReduceBid() public { 23 | vm.startPrank(users.bobPrimary); 24 | clusters.reduceBid(constants.TEST_NAME(), minPrice); 25 | vm.stopPrank(); 26 | 27 | (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder) = 28 | clusters.bids(_stringToBytes32(constants.TEST_NAME())); 29 | uint256 protocolAccrual = clusters.protocolAccrual(); 30 | uint256 totalNameBacking = clusters.totalNameBacking(); 31 | assertEq(minPrice, protocolAccrual + totalNameBacking, "protocolAccrual and totalNameBacking incoherence"); 32 | assertEq(ethAmount, minPrice, "bid value error"); 33 | assertEq(createdTimestamp, constants.MARKET_OPEN_TIMESTAMP() + 1 days, "bid timestamp error"); 34 | assertEq(bidder, _addressToBytes32(users.bobPrimary), "bid bidder error"); 35 | assertBalances(minPrice * 2, protocolAccrual, totalNameBacking, minPrice); 36 | } 37 | 38 | function testReduceBidAll() public { 39 | vm.startPrank(users.bobPrimary); 40 | clusters.reduceBid(constants.TEST_NAME(), minPrice * 2); 41 | vm.stopPrank(); 42 | 43 | (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder) = 44 | clusters.bids(_stringToBytes32(constants.TEST_NAME())); 45 | uint256 protocolAccrual = clusters.protocolAccrual(); 46 | uint256 totalNameBacking = clusters.totalNameBacking(); 47 | assertEq(minPrice, protocolAccrual + totalNameBacking, "protocolAccrual and totalNameBacking incoherence"); 48 | assertEq(ethAmount, 0, "bid value not reset"); 49 | assertEq(createdTimestamp, 0, "bid timestamp not reset"); 50 | assertEq(bidder, bytes32(""), "bid bidder not reset"); 51 | assertBalances(minPrice, protocolAccrual, totalNameBacking, 0); 52 | } 53 | 54 | function testReduceBidOverage() public { 55 | vm.startPrank(users.bobPrimary); 56 | clusters.reduceBid(constants.TEST_NAME(), minPrice * 3); 57 | vm.stopPrank(); 58 | 59 | (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder) = 60 | clusters.bids(_stringToBytes32(constants.TEST_NAME())); 61 | uint256 protocolAccrual = clusters.protocolAccrual(); 62 | uint256 totalNameBacking = clusters.totalNameBacking(); 63 | assertEq(minPrice, protocolAccrual + totalNameBacking, "protocolAccrual and totalNameBacking incoherence"); 64 | assertEq(ethAmount, 0, "bid value not reset"); 65 | assertEq(createdTimestamp, 0, "bid timestamp not reset"); 66 | assertEq(bidder, bytes32(""), "bid bidder not reset"); 67 | assertBalances(minPrice, protocolAccrual, totalNameBacking, 0); 68 | } 69 | 70 | function testReduceBidAfterExpiry() public { 71 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + (10 * 365 days)); 72 | vm.startPrank(users.bobPrimary); 73 | clusters.reduceBid(constants.TEST_NAME(), minPrice * 3); 74 | vm.stopPrank(); 75 | 76 | (uint256 ethAmount, uint256 createdTimestamp, bytes32 bidder) = 77 | clusters.bids(_stringToBytes32(constants.TEST_NAME())); 78 | bytes32[] memory names = new bytes32[](1); 79 | names[0] = _stringToBytes32(constants.TEST_NAME()); 80 | assertEq(ethAmount, 0, "bid value not reset"); 81 | assertEq(createdTimestamp, 0, "bid timestamp not reset"); 82 | assertEq(bidder, bytes32(""), "bid bidder not reset"); 83 | assertBalances(minPrice * 3, minPrice, minPrice * 2, 0); 84 | assertClusterNames(2, 1, names); 85 | } 86 | 87 | function testReduceBid_Reverts() public { 88 | string memory testName = constants.TEST_NAME(); 89 | vm.startPrank(users.bobPrimary); 90 | 91 | vm.expectRevert(IClustersHub.EmptyName.selector); 92 | clusters.reduceBid("", minPrice); 93 | vm.expectRevert(IClustersHub.LongName.selector); 94 | clusters.reduceBid("Privacy is necessary for an open society in the electronic age.", minPrice); 95 | 96 | vm.expectRevert(IClustersHub.NoBid.selector); 97 | clusters.reduceBid("zodomo", minPrice); 98 | vm.expectRevert(IClustersHub.Insufficient.selector); 99 | clusters.reduceBid(testName, minPrice + 1); 100 | 101 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 2 days); 102 | vm.expectRevert(IClustersHub.Timelock.selector); 103 | clusters.reduceBid(testName, minPrice); 104 | vm.stopPrank(); 105 | 106 | vm.prank(users.hacker); 107 | vm.expectRevert(IClustersHub.Unauthorized.selector); 108 | clusters.reduceBid(testName, minPrice); 109 | 110 | bytes memory data = abi.encodeWithSignature("bidName(uint256,string)", minPrice * 3, testName); 111 | fickleReceiver.execute(address(clusters), minPrice * 3, data); 112 | fickleReceiver.toggle(); 113 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 35 days); 114 | data = abi.encodeWithSignature("reduceBid(string,uint256)", testName, minPrice); 115 | vm.expectRevert(IClustersHub.NativeTokenTransferFailed.selector); 116 | fickleReceiver.execute(address(clusters), 0, data); 117 | vm.stopPrank(); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_refundBid.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_refundBid_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 14 | vm.stopPrank(); 15 | 16 | bytes memory data = abi.encodeWithSignature("bidName(uint256,string)", minPrice * 2, constants.TEST_NAME()); 17 | fickleReceiver.execute(address(clusters), minPrice * 2, data); 18 | data = abi.encodeWithSignature("bidName(uint256,string)", minPrice * 2, "zodomo"); 19 | fickleReceiver.execute(address(clusters), minPrice * 2, data); 20 | } 21 | 22 | function testRefundBidTransferBurn() public { 23 | vm.startPrank(users.alicePrimary); 24 | clusters.transferName(constants.TEST_NAME(), 0); 25 | vm.stopPrank(); 26 | assertEq(minPrice * 2, clusters.bidRefunds(_addressToBytes32(address(fickleReceiver))), "bidRefunds incorrect"); 27 | assertBalances(minPrice * 6, minPrice, minPrice, minPrice * 4); 28 | 29 | uint256 balance = address(fickleReceiver).balance; 30 | bytes memory data = abi.encodeWithSignature("refundBid()"); 31 | fickleReceiver.execute(address(clusters), 0, data); 32 | assertEq(address(fickleReceiver).balance, balance + (minPrice * 2), "refund didn't process"); 33 | assertEq(0, clusters.bidRefunds(_addressToBytes32(address(fickleReceiver))), "bidRefunds didn't purge"); 34 | assertBalances(minPrice * 4, minPrice, minPrice, minPrice * 2); 35 | } 36 | 37 | function testRefundBidOutbid() public { 38 | fickleReceiver.toggle(); 39 | 40 | vm.startPrank(users.bidder); 41 | clusters.bidName{value: minPrice * 4}(minPrice * 4, "zodomo"); 42 | vm.stopPrank(); 43 | assertEq(minPrice * 2, clusters.bidRefunds(_addressToBytes32(address(fickleReceiver))), "bidRefunds incorrect"); 44 | assertBalances(minPrice * 10, 0, minPrice * 2, minPrice * 8); 45 | 46 | fickleReceiver.toggle(); 47 | uint256 balance = address(fickleReceiver).balance; 48 | bytes memory data = abi.encodeWithSignature("refundBid()"); 49 | fickleReceiver.execute(address(clusters), 0, data); 50 | assertEq(address(fickleReceiver).balance, balance + (minPrice * 2), "refund didn't process"); 51 | assertEq(0, clusters.bidRefunds(_addressToBytes32(address(fickleReceiver))), "bidRefunds didn't purge"); 52 | assertBalances(minPrice * 8, 0, minPrice * 2, minPrice * 6); 53 | } 54 | 55 | function testRefundBid_Reverts() public { 56 | fickleReceiver.toggle(); 57 | vm.startPrank(users.alicePrimary); 58 | clusters.transferName(constants.TEST_NAME(), 0); 59 | vm.expectRevert(IClustersHub.NoBid.selector); 60 | clusters.refundBid(); 61 | vm.expectRevert(IClustersHub.Unauthorized.selector); 62 | clusters.refundBid(_addressToBytes32(address(fickleReceiver))); 63 | vm.stopPrank(); 64 | 65 | bytes memory data = abi.encodeWithSignature("refundBid()"); 66 | vm.expectRevert(IClustersHub.NativeTokenTransferFailed.selector); 67 | fickleReceiver.execute(address(clusters), 0, data); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_remove.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_remove_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | clusters.add(_addressToBytes32(users.aliceSecondary)); 14 | vm.stopPrank(); 15 | 16 | vm.prank(users.bobPrimary); 17 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 18 | 19 | vm.prank(users.aliceSecondary); 20 | clusters.verify(1); 21 | } 22 | 23 | function testRemove() public { 24 | vm.prank(users.aliceSecondary); 25 | clusters.remove(_addressToBytes32(users.alicePrimary)); 26 | 27 | bytes32[] memory empty; 28 | bytes32[] memory verified = new bytes32[](1); 29 | verified[0] = _addressToBytes32(users.aliceSecondary); 30 | assertUnverifiedAddresses(1, 0, empty); 31 | assertVerifiedAddresses(1, 1, verified); 32 | } 33 | 34 | function testRemoveNamed() public { 35 | vm.prank(users.alicePrimary); 36 | clusters.setWalletName(_addressToBytes32(users.alicePrimary), "Primary"); 37 | 38 | vm.prank(users.aliceSecondary); 39 | clusters.remove(_addressToBytes32(users.alicePrimary)); 40 | 41 | bytes32[] memory empty; 42 | bytes32[] memory verified = new bytes32[](1); 43 | verified[0] = _addressToBytes32(users.aliceSecondary); 44 | assertUnverifiedAddresses(1, 0, empty); 45 | assertVerifiedAddresses(1, 1, verified); 46 | assertEq(bytes32(""), clusters.reverseLookup(_addressToBytes32(users.alicePrimary)), "name not purged"); 47 | } 48 | 49 | function testRemove_Reverts() public { 50 | vm.startPrank(users.hacker); 51 | vm.expectRevert(IClustersHub.NoCluster.selector); 52 | clusters.remove(_addressToBytes32(users.aliceSecondary)); 53 | 54 | vm.expectRevert(IClustersHub.Unauthorized.selector); 55 | clusters.remove(_addressToBytes32(users.alicePrimary), _addressToBytes32(users.aliceSecondary)); 56 | vm.stopPrank(); 57 | 58 | vm.startPrank(users.alicePrimary); 59 | clusters.remove(_addressToBytes32(users.aliceSecondary)); 60 | vm.expectRevert(IClustersHub.Invalid.selector); 61 | clusters.remove(_addressToBytes32(users.alicePrimary)); 62 | vm.stopPrank(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_setDefaultClusterName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_setDefaultClusterName_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 14 | vm.stopPrank(); 15 | vm.prank(users.bobPrimary); 16 | clusters.buyName{value: minPrice}(minPrice, "FOOBAR"); 17 | } 18 | 19 | function testSetDefaultClusterName() public { 20 | vm.prank(users.alicePrimary); 21 | clusters.setDefaultClusterName("zodomo"); 22 | 23 | assertEq(clusters.defaultClusterName(1), _stringToBytes32("zodomo"), "defaultClusterName not updated"); 24 | } 25 | 26 | function testSetDefaultClusterName_Reverts() public { 27 | vm.startPrank(users.hacker); 28 | vm.expectRevert(IClustersHub.EmptyName.selector); 29 | clusters.setDefaultClusterName(""); 30 | vm.expectRevert(IClustersHub.LongName.selector); 31 | clusters.setDefaultClusterName("Privacy is necessary for an open society in the electronic age."); 32 | 33 | vm.expectRevert(IClustersHub.Unauthorized.selector); 34 | clusters.setDefaultClusterName(_addressToBytes32(users.alicePrimary), "zodomo"); 35 | vm.expectRevert(IClustersHub.NoCluster.selector); 36 | clusters.setDefaultClusterName("zodomo"); 37 | vm.stopPrank(); 38 | 39 | vm.prank(users.bobPrimary); 40 | vm.expectRevert(IClustersHub.Unauthorized.selector); 41 | clusters.setDefaultClusterName("zodomo"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_setWalletName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_setWalletName_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | vm.stopPrank(); 14 | } 15 | 16 | function testSetWalletName() public { 17 | vm.prank(users.alicePrimary); 18 | clusters.setWalletName(_addressToBytes32(users.alicePrimary), "Primary"); 19 | 20 | assertWalletName(1, _addressToBytes32(users.alicePrimary), "Primary"); 21 | } 22 | 23 | function testSetWalletNameErase() public { 24 | vm.startPrank(users.alicePrimary); 25 | clusters.setWalletName(_addressToBytes32(users.alicePrimary), "Primary"); 26 | clusters.setWalletName(_addressToBytes32(users.alicePrimary), ""); 27 | vm.stopPrank(); 28 | 29 | assertEq(bytes32(""), clusters.forwardLookup(1, _stringToBytes32("Primary")), "forwardLookup not purged"); 30 | assertEq(bytes32(""), clusters.reverseLookup(_addressToBytes32(users.alicePrimary)), "reverseLookup not purged"); 31 | } 32 | 33 | function testSetWalletName_Reverts() public { 34 | vm.startPrank(users.hacker); 35 | vm.expectRevert(IClustersHub.NoCluster.selector); 36 | clusters.setWalletName(_addressToBytes32(users.alicePrimary), "Primary"); 37 | vm.stopPrank(); 38 | 39 | vm.startPrank(users.alicePrimary); 40 | vm.expectRevert(IClustersHub.LongName.selector); 41 | clusters.setWalletName( 42 | _addressToBytes32(users.alicePrimary), "Privacy is necessary for an open society in the electronic age." 43 | ); 44 | 45 | vm.expectRevert(IClustersHub.Unauthorized.selector); 46 | clusters.setWalletName(_addressToBytes32(users.bobPrimary), "Bob"); 47 | vm.expectRevert(IClustersHub.Unauthorized.selector); 48 | clusters.setWalletName(_addressToBytes32(users.bobPrimary), _addressToBytes32(users.alicePrimary), "Secondary"); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_transferName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_transferName_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | clusters.buyName{value: minPrice}(minPrice, "FOOBAR"); 14 | vm.stopPrank(); 15 | 16 | vm.startPrank(users.bobPrimary); 17 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 18 | vm.stopPrank(); 19 | } 20 | 21 | function testTransferName() public { 22 | vm.startPrank(users.alicePrimary); 23 | clusters.transferName("FOOBAR", 2); 24 | vm.stopPrank(); 25 | 26 | bytes32[] memory names = new bytes32[](1); 27 | names[0] = _stringToBytes32(constants.TEST_NAME()); 28 | assertClusterNames(1, 1, names); 29 | names = new bytes32[](2); 30 | names[0] = _stringToBytes32("zodomo"); 31 | names[1] = _stringToBytes32("FOOBAR"); 32 | assertClusterNames(2, 2, names); 33 | } 34 | 35 | function testTransferNameZeroCluster() public { 36 | vm.startPrank(users.alicePrimary); 37 | clusters.transferName("FOOBAR", 0); 38 | clusters.bidName{value: minPrice}(minPrice, "zodomo"); 39 | vm.stopPrank(); 40 | 41 | vm.prank(users.bobPrimary); 42 | clusters.transferName("zodomo", 0); 43 | 44 | bytes32[] memory empty; 45 | assertClusterNames(0, 0, empty); 46 | assertClusterNames(1, 1, empty); 47 | assertClusterNames(2, 0, empty); 48 | assertBalances(minPrice * 4, minPrice * 2, minPrice, minPrice); 49 | } 50 | 51 | function testTransferNameAll() public { 52 | vm.startPrank(users.alicePrimary); 53 | clusters.transferName("FOOBAR", 2); 54 | clusters.transferName(constants.TEST_NAME(), 2); 55 | vm.stopPrank(); 56 | 57 | bytes32[] memory empty; 58 | assertVerifiedAddresses(1, 0, empty); 59 | assertClusterNames(1, 0, empty); 60 | bytes32[] memory names = new bytes32[](3); 61 | names[0] = _stringToBytes32("zodomo"); 62 | names[1] = _stringToBytes32("FOOBAR"); 63 | names[2] = _stringToBytes32(constants.TEST_NAME()); 64 | assertClusterNames(2, 3, names); 65 | } 66 | 67 | function testTransferName_Reverts() public { 68 | vm.startPrank(users.alicePrimary); 69 | vm.expectRevert(IClustersHub.EmptyName.selector); 70 | clusters.transferName("", 2); 71 | vm.expectRevert(IClustersHub.LongName.selector); 72 | clusters.transferName("Privacy is necessary for an open society in the electronic age.", 2); 73 | 74 | vm.expectRevert(IClustersHub.Unauthorized.selector); 75 | clusters.transferName("zodomo", 1); 76 | vm.expectRevert(IClustersHub.Unauthorized.selector); 77 | clusters.transferName(_addressToBytes32(users.bobPrimary), "FOOBAR", 0); 78 | 79 | vm.expectRevert(IClustersHub.Invalid.selector); 80 | clusters.transferName("FOOBAR", 3); 81 | vm.stopPrank(); 82 | 83 | vm.prank(users.hacker); 84 | vm.expectRevert(IClustersHub.NoCluster.selector); 85 | clusters.transferName(_addressToBytes32(users.hacker), "FOOBAR", 0); 86 | vm.stopPrank(); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Clusters_verify.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Clusters_verify_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function setUp() public virtual override { 9 | PricingHarberger_Unit_Shared_Test.setUp(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | vm.startPrank(users.alicePrimary); 12 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 13 | clusters.add(_addressToBytes32(users.aliceSecondary)); 14 | clusters.add(_addressToBytes32(users.bobPrimary)); 15 | vm.stopPrank(); 16 | 17 | vm.prank(users.bobPrimary); 18 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 19 | 20 | vm.prank(users.bobSecondary); 21 | clusters.buyName{value: minPrice}(minPrice, "FOOBAR"); 22 | } 23 | 24 | function testVerify() public { 25 | vm.prank(users.aliceSecondary); 26 | clusters.verify(1); 27 | 28 | bytes32[] memory empty; 29 | bytes32[] memory verified = new bytes32[](2); 30 | verified[0] = _addressToBytes32(users.alicePrimary); 31 | verified[1] = _addressToBytes32(users.aliceSecondary); 32 | assertUnverifiedAddresses(1, 1, empty); 33 | assertVerifiedAddresses(1, 2, verified); 34 | } 35 | 36 | function testVerifyInCluster() public { 37 | vm.prank(users.bobPrimary); 38 | clusters.verify(1); 39 | 40 | bytes32[] memory empty; 41 | bytes32[] memory verified = new bytes32[](2); 42 | bytes32[] memory names = new bytes32[](2); 43 | verified[0] = _addressToBytes32(users.alicePrimary); 44 | verified[1] = _addressToBytes32(users.bobPrimary); 45 | names[0] = _stringToBytes32(constants.TEST_NAME()); 46 | names[1] = _stringToBytes32("zodomo"); 47 | assertUnverifiedAddresses(1, 1, empty); 48 | assertVerifiedAddresses(1, 2, verified); 49 | assertClusterNames(1, 2, names); 50 | 51 | vm.prank(users.bobSecondary); 52 | clusters.add(_addressToBytes32(users.bobPrimary)); 53 | 54 | vm.prank(users.bobPrimary); 55 | clusters.verify(3); 56 | 57 | verified = new bytes32[](2); 58 | verified[0] = _addressToBytes32(users.bobSecondary); 59 | verified[1] = _addressToBytes32(users.bobPrimary); 60 | assertUnverifiedAddresses(3, 0, empty); 61 | assertVerifiedAddresses(3, 2, verified); 62 | verified = new bytes32[](1); 63 | verified[0] = _addressToBytes32(users.alicePrimary); 64 | assertVerifiedAddresses(1, 1, verified); 65 | assertVerifiedAddresses(2, 0, empty); 66 | } 67 | 68 | function testVerify_Reverts() public { 69 | vm.startPrank(users.hacker); 70 | vm.expectRevert(IClustersHub.Unauthorized.selector); 71 | clusters.verify(_addressToBytes32(users.alicePrimary), 1); 72 | vm.expectRevert(IClustersHub.Unauthorized.selector); 73 | clusters.verify(1); 74 | vm.stopPrank(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Endpoint_ECDSA.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {ECDSA} from "solady/utils/ECDSA.sol"; 6 | 7 | contract Endpoint_ECDSA_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function testECDSAGeneralOrder() public { 9 | string memory testName = constants.TEST_NAME(); 10 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 11 | 12 | vm.startPrank(users.signer); 13 | clusters.buyName{value: minPrice}(minPrice, testName); 14 | 15 | bytes32 messageHash = endpointProxy.getOrderHash( 16 | 0, constants.MARKET_OPEN_TIMESTAMP() + 2 days, minPrice * 3, bytes32(""), testName 17 | ); 18 | bytes32 digest = endpointProxy.getEthSignedMessageHash(messageHash); 19 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(users.signerPrivKey, digest); 20 | bytes memory sig = abi.encodePacked(r, s, v); 21 | vm.stopPrank(); 22 | 23 | bool valid = endpointProxy.verifyOrder( 24 | 0, constants.MARKET_OPEN_TIMESTAMP() + 2 days, minPrice * 3, bytes32(""), testName, sig, users.signer 25 | ); 26 | assertEq(valid, true, "ECDSA verification error"); 27 | } 28 | 29 | function testECDSASpecificOrder() public { 30 | string memory testName = constants.TEST_NAME(); 31 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 32 | 33 | vm.startPrank(users.signer); 34 | clusters.buyName{value: minPrice}(minPrice, testName); 35 | 36 | bytes32 messageHash = endpointProxy.getOrderHash( 37 | 0, constants.MARKET_OPEN_TIMESTAMP() + 2 days, minPrice * 3, _addressToBytes32(users.alicePrimary), testName 38 | ); 39 | bytes32 digest = endpointProxy.getEthSignedMessageHash(messageHash); 40 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(users.signerPrivKey, digest); 41 | bytes memory sig = abi.encodePacked(r, s, v); 42 | vm.stopPrank(); 43 | 44 | bool valid = endpointProxy.verifyOrder( 45 | 0, 46 | constants.MARKET_OPEN_TIMESTAMP() + 2 days, 47 | minPrice * 3, 48 | _addressToBytes32(users.alicePrimary), 49 | testName, 50 | sig, 51 | users.signer 52 | ); 53 | assertEq(valid, true, "ECDSA verification error"); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Endpoint_fulfillOrder.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {ECDSA} from "solady/utils/ECDSA.sol"; 6 | import {console2} from "forge-std/Test.sol"; 7 | 8 | contract Endpoint_fulfillOrder_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 9 | function setUp() public virtual override { 10 | PricingHarberger_Unit_Shared_Test.setUp(); 11 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 12 | string memory testName = constants.TEST_NAME(); 13 | 14 | vm.startPrank(users.signer); 15 | clusters.buyName{value: minPrice}(minPrice, testName); 16 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 17 | vm.stopPrank(); 18 | } 19 | 20 | function testFulfillOrder() public { 21 | vm.startPrank(users.signer); 22 | string memory testName = constants.TEST_NAME(); 23 | bytes32 messageHash = endpointProxy.getOrderHash( 24 | 0, constants.MARKET_OPEN_TIMESTAMP() + 2 days, minPrice * 3, bytes32(""), testName 25 | ); 26 | bytes32 digest = endpointProxy.getEthSignedMessageHash(messageHash); 27 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(users.signerPrivKey, digest); 28 | bytes memory sig = abi.encodePacked(r, s, v); 29 | vm.stopPrank(); 30 | 31 | vm.startPrank(users.alicePrimary); 32 | endpointProxy.fulfillOrder{value: minPrice * 3}( 33 | minPrice * 3, 0, constants.MARKET_OPEN_TIMESTAMP() + 2 days, bytes32(""), testName, sig, users.signer 34 | ); 35 | vm.stopPrank(); 36 | 37 | vm.startPrank(users.signer); 38 | messageHash = endpointProxy.getOrderHash( 39 | 2, constants.MARKET_OPEN_TIMESTAMP() + 2 days, minPrice * 3, _addressToBytes32(users.alicePrimary), "zodomo" 40 | ); 41 | digest = endpointProxy.getEthSignedMessageHash(messageHash); 42 | (v, r, s) = vm.sign(users.signerPrivKey, digest); 43 | sig = abi.encodePacked(r, s, v); 44 | console2.log( 45 | endpointProxy.verifyOrder( 46 | 2, 47 | constants.MARKET_OPEN_TIMESTAMP() + 2 days, 48 | minPrice * 3, 49 | _addressToBytes32(users.alicePrimary), 50 | "zodomo", 51 | sig, 52 | users.signer 53 | ) 54 | ); 55 | vm.stopPrank(); 56 | 57 | vm.startPrank(users.alicePrimary); 58 | endpointProxy.fulfillOrder{value: minPrice * 3}( 59 | minPrice * 3, 60 | 2, 61 | constants.MARKET_OPEN_TIMESTAMP() + 2 days, 62 | _addressToBytes32(users.alicePrimary), 63 | "zodomo", 64 | sig, 65 | users.signer 66 | ); 67 | vm.stopPrank(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Endpoint_multicall.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {ECDSA} from "solady/utils/ECDSA.sol"; 6 | 7 | contract Endpoint_buyName_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function testMulticall() public { 9 | bytes32 caller = _addressToBytes32(users.alicePrimary); 10 | string memory testName = constants.TEST_NAME(); 11 | bytes[] memory data = new bytes[](5); 12 | data[0] = abi.encodeWithSignature("buyName(bytes32,uint256,string)", caller, minPrice, testName); 13 | data[1] = abi.encodeWithSignature("fundName(bytes32,uint256,string)", caller, minPrice, testName); 14 | data[2] = abi.encodeWithSignature("add(bytes32,bytes32)", caller, _addressToBytes32(users.aliceSecondary)); 15 | data[3] = abi.encodeWithSignature("setWalletName(bytes32,bytes32,string)", caller, caller, "Primary"); 16 | data[4] = abi.encodeWithSignature( 17 | "setWalletName(bytes32,bytes32,string)", caller, _addressToBytes32(users.aliceSecondary), "Secondary" 18 | ); 19 | 20 | vm.startPrank(users.signer); 21 | bytes32 messageHash = endpointProxy.getMulticallHash(data); 22 | bytes32 digest = endpointProxy.getEthSignedMessageHash(messageHash); 23 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(users.signerPrivKey, digest); 24 | bytes memory sig = abi.encodePacked(r, s, v); 25 | vm.stopPrank(); 26 | 27 | vm.prank(users.alicePrimary); 28 | endpointProxy.multicall{value: minPrice * 2}(data, sig); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Endpoint_setClustersAddr.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Endpoint_setClustersAddr_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function testSetClustersAddr() public { 9 | address testAddr = constants.TEST_ADDRESS(); 10 | assertEndpointVars(address(clusters), users.signer); 11 | 12 | vm.prank(users.clustersAdmin); 13 | endpointProxy.setClustersAddr(testAddr); 14 | 15 | assertEndpointVars(testAddr, users.signer); 16 | } 17 | 18 | function testSetClustersAddr_RevertUnauthorized() public { 19 | address testAddr = constants.TEST_ADDRESS(); 20 | vm.prank(users.hacker); 21 | vm.expectRevert(abi.encodeWithSignature("OwnableUnauthorizedAccount(address)", users.hacker)); 22 | endpointProxy.setClustersAddr(testAddr); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Endpoint_setSignerAddr.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {IClustersHub} from "clusters/interfaces/IClustersHub.sol"; 6 | 7 | contract Endpoint_setSignerAddr_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 8 | function testSetSignerAddr() public { 9 | address testAddr = constants.TEST_ADDRESS(); 10 | assertEndpointVars(address(clusters), users.signer); 11 | 12 | vm.prank(users.clustersAdmin); 13 | endpointProxy.setSignerAddr(testAddr); 14 | 15 | assertEndpointVars(address(clusters), testAddr); 16 | } 17 | 18 | function testSetSignerAddr_RevertUnauthorized() public { 19 | address testAddr = constants.TEST_ADDRESS(); 20 | vm.prank(users.hacker); 21 | vm.expectRevert(abi.encodeWithSignature("OwnableUnauthorizedAccount(address)", users.hacker)); 22 | endpointProxy.setSignerAddr(testAddr); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Endpoint_upgradeToAndCall.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {Endpoint} from "clusters/Endpoint.sol"; 6 | import {IEndpoint} from "clusters/interfaces/IEndpoint.sol"; 7 | import {IUUPS} from "clusters/interfaces/IUUPS.sol"; 8 | 9 | contract Endpoint_upgradeToAndCall_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 10 | function testUpgradeToAndCall() public { 11 | IEndpoint newEndpoint = new Endpoint(); 12 | vm.label(address(newEndpoint), "New Endpoint Implementation"); 13 | 14 | vm.prank(users.hacker); 15 | vm.expectRevert(); 16 | IUUPS(address(endpointProxy)).upgradeToAndCall(address(newEndpoint), bytes("")); 17 | 18 | vm.prank(users.clustersAdmin); 19 | IUUPS(address(endpointProxy)).upgradeToAndCall(address(newEndpoint), bytes("")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/unit/local/concrete/PricingHarberger.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | 6 | contract PricingHarberger_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 7 | uint256 secondsSinceDeployment = 1000 * 365 days; 8 | 9 | function setUp() public virtual override { 10 | PricingHarberger_Unit_Shared_Test.setUp(); 11 | } 12 | 13 | function testDecayMultiplier() public { 14 | uint256 decay = pricingHarberger.exposed_getDecayMultiplier(730 days); 15 | assertEq(decay, 0.25e18 - 1); // Tiny error tolerance is okay 16 | } 17 | 18 | function testMaxIntersectionZeroSecondsSinceDeployment() public { 19 | uint256 intersectionBasic = pricingHarberger.exposed_getMaxIntersection(0.02 ether, 0e18); 20 | assertLt(intersectionBasic, 9e12); // Tiny error tolerance 21 | 22 | uint256 intersectionOne = pricingHarberger.exposed_getMaxIntersection(1 ether, 0e18); 23 | assertEq(intersectionOne, 4047534052094804142); // 4.04 years 24 | 25 | uint256 intersectionHundred = pricingHarberger.exposed_getMaxIntersection(100 ether, 0e18); 26 | assertEq(intersectionHundred, 9735009655744918055); // 9.7 years 27 | } 28 | 29 | function testMaxIntersectionTenYearsSinceDeployment() public { 30 | // TODO: Is numerical drift as the yearsSinceDeployment increases an issue? 31 | uint256 intersectionBasic = pricingHarberger.exposed_getMaxIntersection(0.12 ether, 10e18); 32 | assertLt(intersectionBasic, 9e13); 33 | 34 | uint256 intersectionOne = pricingHarberger.exposed_getMaxIntersection(1 ether, 10e18); 35 | assertEq(intersectionOne, 2760261962592581821); // 2.7 years 36 | 37 | uint256 intersectionHundred = pricingHarberger.exposed_getMaxIntersection(100 ether, 10e18); 38 | assertEq(intersectionHundred, 8902202613552457583); // 8.7 years 39 | } 40 | 41 | function testMinIntersection() public { 42 | assertEq(pricingHarberger.exposed_getMinIntersection(0.01 ether), 0e18); 43 | assertEq(pricingHarberger.exposed_getMinIntersection(0.02 ether), 1e18); 44 | assertEq(pricingHarberger.exposed_getMinIntersection(0.16 ether), 4e18 - 3); 45 | assertEq(pricingHarberger.exposed_getMinIntersection(1 ether), 6643856189774724690); // 6 years 46 | } 47 | 48 | function testIntegratedDecayPrice() public { 49 | uint256 spent = pricingHarberger.exposed_getIntegratedDecayPrice(1 ether, 730 days); 50 | assertEq(spent, 1082021280666722556); // 1.08 ether over 2 years 51 | } 52 | 53 | function testIntegratedPriceSimpleMin() public { 54 | (uint256 simpleMinSpent, uint256 simpleMinPrice) = pricingHarberger.getIntegratedPrice(minPrice, 730 days); 55 | assertEq(simpleMinSpent, 2 * minPrice); 56 | assertEq(simpleMinPrice, minPrice); 57 | } 58 | 59 | function testIntegratedPriceSimpleDecay() public { 60 | vm.warp(block.timestamp + secondsSinceDeployment); // Simulate large secondsSinceDeployment value 61 | (uint256 simpleDecaySpent, uint256 simpleDecayPrice) = pricingHarberger.getIntegratedPrice(1 ether, 730 days); 62 | assertEq(simpleDecaySpent, 1082021280666722556); // 1.08 ether over 2 years 63 | assertEq(simpleDecayPrice, 0.25e18 - 1); // Cut in half every year, now a quarter of start price 64 | } 65 | 66 | function testIntegratedPriceSimpleDecay2() public { 67 | vm.warp(block.timestamp + secondsSinceDeployment); // Simulate large secondsSinceDeployment value 68 | (uint256 simpleDecaySpent2, uint256 simpleDecayPrice2) = pricingHarberger.getIntegratedPrice(1 ether, 209520648); 69 | assertEq(simpleDecaySpent2, 1428268090226162139); // 1.42 ether over 6.64 years 70 | assertEq(simpleDecayPrice2, 10000000175998132); // ~0.01 price after 6.64 years 71 | } 72 | 73 | function testIntegratedPriceComplexDecay() public { 74 | vm.warp(block.timestamp + secondsSinceDeployment); // Simulate large secondsSinceDeployment value 75 | (uint256 complexDecaySpent, uint256 complexDecayPrice) = 76 | pricingHarberger.getIntegratedPrice(1 ether, 10 * 365 days); 77 | assertEq(complexDecaySpent, 1461829528582326522); // 1.42 ether over 6.6 years then 0.03 ether over 3 years 78 | assertEq(complexDecayPrice, minPrice); 79 | } 80 | 81 | function testIntegratedPriceSimpleMax() public { 82 | (uint256 simpleMaxSpent, uint256 simpleMaxPrice) = pricingHarberger.getIntegratedPrice(1 ether, 365 days); 83 | assertEq(simpleMaxSpent, 0.025 ether); 84 | assertEq(simpleMaxPrice, 0.5 ether - 1); // Actual price has decayed by half before being truncated by max 85 | } 86 | 87 | function testIntegratedPriceMaxToMiddleRange() public { 88 | vm.warp(block.timestamp + secondsSinceDeployment); // Simulate large secondsSinceDeployment value 89 | (uint256 maxToMiddleSpent, uint256 maxToMiddlePrice) = 90 | pricingHarberger.getIntegratedPrice(0.025 ether, 365 days); 91 | assertEq(maxToMiddleSpent, 18033688011112042); // 0.018 ether for 1 year that dips from max into middle 92 | assertEq(maxToMiddlePrice, 0.0125 ether - 1); 93 | } 94 | 95 | function testIntegratedPriceMaxToMinRange() public { 96 | vm.warp(block.timestamp + secondsSinceDeployment); // Simulate large secondsSinceDeployment value 97 | (uint256 maxToMinSpent, uint256 maxToMinPrice) = pricingHarberger.getIntegratedPrice(0.025 ether, 730 days); 98 | assertEq(maxToMinSpent, 28421144664460826); // 0.028 ether for 2 year that dips from max into middle to min 99 | assertEq(maxToMinPrice, 0.01 ether); 100 | 101 | (maxToMinSpent, maxToMinPrice) = pricingHarberger.getIntegratedPrice(0.025 ether, 3 * 365 days); 102 | assertEq(maxToMinSpent, 38421144664460826); // 0.028 ether for 2 year that dips from max into middle to min 103 | assertEq(maxToMinPrice, 0.01 ether); 104 | } 105 | 106 | function testPriceAfterBid() public { 107 | uint256 newPrice = pricingHarberger.exposed_getPriceAfterBid(1 ether, 2 ether, 0); 108 | assertEq(newPrice, 1 ether); 109 | 110 | newPrice = pricingHarberger.exposed_getPriceAfterBid(1 ether, 2 ether, 15 days); 111 | assertEq(newPrice, 1.25 ether); 112 | 113 | newPrice = pricingHarberger.exposed_getPriceAfterBid(1 ether, 2 ether, 30 days); 114 | assertEq(newPrice, 2 ether); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /test/unit/local/concrete/Pricing_upgradeToAndCall.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {PricingHarberger_Unit_Shared_Test} from "../shared/SharedPricingHarberger.t.sol"; 5 | import {PricingHarberger} from "clusters/PricingHarberger.sol"; 6 | import {IPricing} from "clusters/interfaces/IPricing.sol"; 7 | import {IUUPS} from "clusters/interfaces/IUUPS.sol"; 8 | 9 | contract Pricing_upgradeToAndCall_Unit_Concrete_Test is PricingHarberger_Unit_Shared_Test { 10 | function testUpgradeToAndCall() public { 11 | IPricing newPricing = new PricingHarberger(); 12 | vm.label(address(newPricing), "New Pricing Implementation"); 13 | 14 | vm.prank(users.hacker); 15 | vm.expectRevert(); 16 | IUUPS(address(pricingProxy)).upgradeToAndCall(address(newPricing), bytes("")); 17 | 18 | vm.prank(users.clustersAdmin); 19 | IUUPS(address(pricingProxy)).upgradeToAndCall(address(newPricing), bytes("")); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/unit/local/shared/SharedPricingFlat.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "../../../Base.t.sol"; 5 | 6 | abstract contract PricingFlat_Unit_Shared_Test is Base_Test { 7 | using EnumerableSet for EnumerableSet.AddressSet; 8 | 9 | function setUp() public virtual override { 10 | Base_Test.setUp(); 11 | configureHarbergerEnvironment(); 12 | endpointProxy = IEndpoint(endpointGroup.at(0)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/unit/local/shared/SharedPricingHarberger.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import "../../../Base.t.sol"; 5 | 6 | abstract contract PricingHarberger_Unit_Shared_Test is Base_Test { 7 | using EnumerableSet for EnumerableSet.AddressSet; 8 | 9 | function setUp() public virtual override { 10 | Base_Test.setUp(); 11 | configureHarbergerEnvironment(); 12 | endpointProxy = IEndpoint(endpointGroup.at(0)); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_add.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_add_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function setUp() public virtual override { 11 | Inbound_Harberger_Shared_Test.setUp(); 12 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 13 | vm.startPrank(users.alicePrimary); 14 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 15 | vm.stopPrank(); 16 | } 17 | 18 | function testAdd() public { 19 | vm.startPrank(users.alicePrimary); 20 | bytes memory data = abi.encodeWithSignature( 21 | "add(bytes32,bytes32)", _addressToBytes32(users.alicePrimary), _addressToBytes32(users.aliceSecondary) 22 | ); 23 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, 0); 24 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 25 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 26 | //verifyPackets(1, address(localEndpoint)); 27 | vm.stopPrank(); 28 | 29 | bytes32[] memory unverified = new bytes32[](1); 30 | unverified[0] = _addressToBytes32(users.aliceSecondary); 31 | bytes32[] memory verified = new bytes32[](1); 32 | verified[0] = _addressToBytes32(users.alicePrimary); 33 | bytes32[] memory names = new bytes32[](1); 34 | names[0] = _stringToBytes32(constants.TEST_NAME()); 35 | assertBalances(1, minPrice, 0, minPrice, 0); 36 | assertNameBacking(1, constants.TEST_NAME(), minPrice); 37 | assertUnverifiedAddresses(1, 1, 1, unverified); 38 | assertVerifiedAddresses(1, 1, 1, verified); 39 | assertClusterNames(1, 1, 1, names); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_bidName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_bidName_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function setUp() public virtual override { 11 | Inbound_Harberger_Shared_Test.setUp(); 12 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 13 | vm.startPrank(users.alicePrimary); 14 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 15 | vm.stopPrank(); 16 | } 17 | 18 | function testBidName() public { 19 | vm.startPrank(users.bobPrimary); 20 | bytes memory data = abi.encodeWithSignature( 21 | "bidName(bytes32,uint256,string)", _addressToBytes32(users.bobPrimary), minPrice * 2, constants.TEST_NAME() 22 | ); 23 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, uint128(minPrice * 2)); 24 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 25 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 26 | //verifyPackets(1, address(localEndpoint)); 27 | vm.stopPrank(); 28 | 29 | bytes32[] memory unverified; 30 | bytes32[] memory verified = new bytes32[](1); 31 | verified[0] = _addressToBytes32(users.alicePrimary); 32 | bytes32[] memory names = new bytes32[](1); 33 | names[0] = _stringToBytes32(constants.TEST_NAME()); 34 | assertBalances(1, minPrice * 3, 0, minPrice, minPrice * 2); 35 | assertBid( 36 | 1, 37 | constants.TEST_NAME(), 38 | minPrice * 2, 39 | constants.MARKET_OPEN_TIMESTAMP() + 1 days, 40 | _addressToBytes32(users.bobPrimary) 41 | ); 42 | assertNameBacking(1, constants.TEST_NAME(), minPrice); 43 | assertUnverifiedAddresses(1, 1, 0, unverified); 44 | assertVerifiedAddresses(1, 1, 1, verified); 45 | assertClusterNames(1, 1, 1, names); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_buyName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test, console2} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_buyName_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function testBuyName() public { 11 | vm.startPrank(users.alicePrimary); 12 | bytes memory data = abi.encodeWithSignature( 13 | "buyName(bytes32,uint256,string)", _addressToBytes32(users.alicePrimary), minPrice, constants.TEST_NAME() 14 | ); 15 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(500_000, uint128(minPrice)); 16 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 17 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 18 | //verifyPackets(1, address(localEndpoint)); 19 | vm.stopPrank(); 20 | 21 | bytes32[] memory unverified; 22 | bytes32[] memory verified = new bytes32[](1); 23 | verified[0] = _addressToBytes32(users.alicePrimary); 24 | bytes32[] memory names = new bytes32[](1); 25 | names[0] = _stringToBytes32(constants.TEST_NAME()); 26 | assertBalances(1, minPrice, 0, minPrice, 0); 27 | assertNameBacking(1, constants.TEST_NAME(), minPrice); 28 | assertUnverifiedAddresses(1, 1, 0, unverified); 29 | assertVerifiedAddresses(1, 1, 1, verified); 30 | assertClusterNames(1, 1, 1, names); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_fundName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_fundName_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function setUp() public virtual override { 11 | Inbound_Harberger_Shared_Test.setUp(); 12 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 13 | vm.startPrank(users.alicePrimary); 14 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 15 | vm.stopPrank(); 16 | } 17 | 18 | function testFundName() public { 19 | vm.startPrank(users.alicePrimary); 20 | bytes memory data = abi.encodeWithSignature( 21 | "fundName(bytes32,uint256,string)", _addressToBytes32(users.alicePrimary), minPrice, constants.TEST_NAME() 22 | ); 23 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, uint128(minPrice)); 24 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 25 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 26 | //verifyPackets(1, address(localEndpoint)); 27 | vm.stopPrank(); 28 | 29 | bytes32[] memory unverified; 30 | bytes32[] memory verified = new bytes32[](1); 31 | verified[0] = _addressToBytes32(users.alicePrimary); 32 | bytes32[] memory names = new bytes32[](1); 33 | names[0] = _stringToBytes32(constants.TEST_NAME()); 34 | assertBalances(1, minPrice * 2, 0, minPrice * 2, 0); 35 | assertNameBacking(1, constants.TEST_NAME(), minPrice * 2); 36 | assertUnverifiedAddresses(1, 1, 0, unverified); 37 | assertVerifiedAddresses(1, 1, 1, verified); 38 | assertClusterNames(1, 1, 1, names); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_gasAirdrop.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_gasAirdrop_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function testGasAirdrop() public { 11 | vm.startPrank(users.alicePrimary); 12 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(50_000, 0) 13 | .addExecutorNativeDropOption(uint128(minPrice), _addressToBytes32(users.aliceSecondary)); 14 | uint256 balance = address(users.aliceSecondary).balance; 15 | (uint256 nativeFee,) = remoteEndpoint.quote(1, bytes(""), options, false); 16 | remoteEndpoint.gasAirdrop{value: nativeFee}(nativeFee, 1, options); 17 | //verifyPackets(1, address(localEndpoint)); 18 | vm.stopPrank(); 19 | assertEq(balance + minPrice, address(users.aliceSecondary).balance, "airdrop balance error"); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_pokeName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_pokeName_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function setUp() public virtual override { 11 | Inbound_Harberger_Shared_Test.setUp(); 12 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 13 | vm.startPrank(users.alicePrimary); 14 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 15 | vm.stopPrank(); 16 | } 17 | 18 | function testPokeName() public { 19 | vm.startPrank(users.bobPrimary); 20 | bytes memory data = abi.encodeWithSignature("pokeName(string)", constants.TEST_NAME()); 21 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, 0); 22 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 23 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 24 | //verifyPackets(1, address(localEndpoint)); 25 | vm.stopPrank(); 26 | 27 | bytes32[] memory unverified; 28 | bytes32[] memory verified = new bytes32[](1); 29 | verified[0] = _addressToBytes32(users.alicePrimary); 30 | bytes32[] memory names = new bytes32[](1); 31 | names[0] = _stringToBytes32(constants.TEST_NAME()); 32 | assertBalances(1, minPrice, 0, minPrice, 0); 33 | assertNameBacking(1, constants.TEST_NAME(), minPrice); 34 | assertUnverifiedAddresses(1, 1, 0, unverified); 35 | assertVerifiedAddresses(1, 1, 1, verified); 36 | assertClusterNames(1, 1, 1, names); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_refund.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test, console2} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_refund_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function testRefundEx() public { 11 | vm.startPrank(users.alicePrimary); 12 | bytes memory data = abi.encodeWithSignature( 13 | "buyName(bytes32,uint256,string)", _addressToBytes32(users.alicePrimary), minPrice, constants.TEST_NAME() 14 | ); 15 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(500_000, uint128(minPrice / 2)); 16 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 17 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 18 | vm.stopPrank(); 19 | //verifyPackets(1, address(localEndpoint)); 20 | 21 | assertBalances(1, 0, 0, 0, 0); 22 | assertEq(minPrice / 2, address(localEndpoint).balance, "endpoint balance error"); 23 | assertEq( 24 | minPrice / 2, localEndpoint.failedTxRefunds(_addressToBytes32(users.alicePrimary)), "failedTxRefunds error" 25 | ); 26 | 27 | uint256 balance = address(users.alicePrimary).balance; 28 | vm.prank(users.alicePrimary); 29 | localEndpoint.refund(); 30 | assertEq(0, address(localEndpoint).balance, "endpoint balance did not decrease"); 31 | assertEq(balance + (minPrice / 2), address(users.alicePrimary).balance, "refund not issued"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_remove.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_remove_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function setUp() public virtual override { 11 | Inbound_Harberger_Shared_Test.setUp(); 12 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 13 | vm.startPrank(users.alicePrimary); 14 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 15 | clusters.add(_addressToBytes32(users.aliceSecondary)); 16 | vm.stopPrank(); 17 | 18 | vm.prank(users.aliceSecondary); 19 | clusters.verify(1); 20 | } 21 | 22 | function testRemove() public { 23 | vm.startPrank(users.aliceSecondary); 24 | bytes memory data = abi.encodeWithSignature( 25 | "remove(bytes32,bytes32)", _addressToBytes32(users.aliceSecondary), _addressToBytes32(users.alicePrimary) 26 | ); 27 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, 0); 28 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 29 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 30 | //verifyPackets(1, address(localEndpoint)); 31 | vm.stopPrank(); 32 | 33 | bytes32[] memory unverified; 34 | bytes32[] memory verified = new bytes32[](1); 35 | verified[0] = _addressToBytes32(users.aliceSecondary); 36 | bytes32[] memory names = new bytes32[](1); 37 | names[0] = _stringToBytes32(constants.TEST_NAME()); 38 | assertBalances(1, minPrice, 0, minPrice, 0); 39 | assertNameBacking(1, constants.TEST_NAME(), minPrice); 40 | assertUnverifiedAddresses(1, 1, 0, unverified); 41 | assertVerifiedAddresses(1, 1, 1, verified); 42 | assertClusterNames(1, 1, 1, names); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_setDefaultClusterName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_setDefaultClusterName_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function setUp() public virtual override { 11 | Inbound_Harberger_Shared_Test.setUp(); 12 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 13 | vm.startPrank(users.alicePrimary); 14 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 15 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 16 | vm.stopPrank(); 17 | } 18 | 19 | function testSetDefaultClusterName() public { 20 | vm.startPrank(users.alicePrimary); 21 | bytes memory data = abi.encodeWithSignature( 22 | "setDefaultClusterName(bytes32,string)", _addressToBytes32(users.alicePrimary), "zodomo" 23 | ); 24 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, 0); 25 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 26 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 27 | //verifyPackets(1, address(localEndpoint)); 28 | vm.stopPrank(); 29 | 30 | bytes32[] memory unverified; 31 | bytes32[] memory verified = new bytes32[](1); 32 | verified[0] = _addressToBytes32(users.alicePrimary); 33 | bytes32[] memory names = new bytes32[](2); 34 | names[0] = _stringToBytes32(constants.TEST_NAME()); 35 | names[1] = _stringToBytes32("zodomo"); 36 | assertBalances(1, minPrice * 2, 0, minPrice * 2, 0); 37 | assertNameBacking(1, constants.TEST_NAME(), minPrice); 38 | assertUnverifiedAddresses(1, 1, 0, unverified); 39 | assertVerifiedAddresses(1, 1, 1, verified); 40 | assertClusterNames(1, 1, 2, names); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_setWalletName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_setWalletName_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function setUp() public virtual override { 11 | Inbound_Harberger_Shared_Test.setUp(); 12 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 13 | vm.startPrank(users.alicePrimary); 14 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 15 | vm.stopPrank(); 16 | } 17 | 18 | function testSetWalletName() public { 19 | vm.startPrank(users.alicePrimary); 20 | bytes memory data = abi.encodeWithSignature( 21 | "setWalletName(bytes32,bytes32,string)", 22 | _addressToBytes32(users.alicePrimary), 23 | _addressToBytes32(users.alicePrimary), 24 | "Primary" 25 | ); 26 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, 0); 27 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 28 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 29 | //verifyPackets(1, address(localEndpoint)); 30 | vm.stopPrank(); 31 | 32 | bytes32[] memory unverified; 33 | bytes32[] memory verified = new bytes32[](1); 34 | verified[0] = _addressToBytes32(users.alicePrimary); 35 | bytes32[] memory names = new bytes32[](1); 36 | names[0] = _stringToBytes32(constants.TEST_NAME()); 37 | assertBalances(1, minPrice, 0, minPrice, 0); 38 | assertNameBacking(1, constants.TEST_NAME(), minPrice); 39 | assertUnverifiedAddresses(1, 1, 0, unverified); 40 | assertVerifiedAddresses(1, 1, 1, verified); 41 | assertClusterNames(1, 1, 1, names); 42 | assertWalletName(1, 1, _addressToBytes32(users.alicePrimary), "Primary"); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_transferName.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_transferName_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function setUp() public virtual override { 11 | Inbound_Harberger_Shared_Test.setUp(); 12 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 13 | vm.startPrank(users.alicePrimary); 14 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 15 | clusters.buyName{value: minPrice}(minPrice, "FOOBAR"); 16 | vm.stopPrank(); 17 | 18 | vm.startPrank(users.bobPrimary); 19 | clusters.buyName{value: minPrice}(minPrice, "zodomo"); 20 | vm.stopPrank(); 21 | } 22 | 23 | function testTransferName() public { 24 | vm.startPrank(users.alicePrimary); 25 | bytes memory data = abi.encodeWithSignature( 26 | "transferName(bytes32,string,uint256)", _addressToBytes32(users.alicePrimary), "FOOBAR", 2 27 | ); 28 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, 0); 29 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 30 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 31 | //verifyPackets(1, address(localEndpoint)); 32 | vm.stopPrank(); 33 | 34 | bytes32[] memory unverified; 35 | bytes32[] memory verified = new bytes32[](1); 36 | verified[0] = _addressToBytes32(users.bobPrimary); 37 | bytes32[] memory names = new bytes32[](2); 38 | names[0] = _stringToBytes32("zodomo"); 39 | names[1] = _stringToBytes32("FOOBAR"); 40 | assertBalances(1, minPrice * 3, 0, minPrice * 3, 0); 41 | assertNameBacking(1, "FOOBAR", minPrice); 42 | assertUnverifiedAddresses(1, 2, 0, unverified); 43 | assertVerifiedAddresses(1, 2, 1, verified); 44 | assertClusterNames(1, 2, 2, names); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/inbound/Endpoint_verify.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | contract Inbound_Endpoint_verify_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 8 | using OptionsBuilder for bytes; 9 | 10 | function setUp() public virtual override { 11 | Inbound_Harberger_Shared_Test.setUp(); 12 | vm.warp(constants.MARKET_OPEN_TIMESTAMP() + 1 days); 13 | vm.startPrank(users.alicePrimary); 14 | clusters.buyName{value: minPrice}(minPrice, constants.TEST_NAME()); 15 | clusters.add(_addressToBytes32(users.aliceSecondary)); 16 | vm.stopPrank(); 17 | } 18 | 19 | function testVerify() public { 20 | vm.startPrank(users.aliceSecondary); 21 | bytes memory data = 22 | abi.encodeWithSignature("verify(bytes32,uint256)", _addressToBytes32(users.aliceSecondary), 1); 23 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(250_000, 0); 24 | (uint256 nativeFee,) = remoteEndpoint.quote(1, data, options, false); 25 | remoteEndpoint.lzSend{value: nativeFee}(data, options, payable(msg.sender)); 26 | //verifyPackets(1, address(localEndpoint)); 27 | vm.stopPrank(); 28 | 29 | bytes32[] memory unverified; 30 | bytes32[] memory verified = new bytes32[](2); 31 | verified[0] = _addressToBytes32(users.alicePrimary); 32 | verified[1] = _addressToBytes32(users.aliceSecondary); 33 | bytes32[] memory names = new bytes32[](1); 34 | names[0] = _stringToBytes32(constants.TEST_NAME()); 35 | assertBalances(1, minPrice, 0, minPrice, 0); 36 | assertNameBacking(1, constants.TEST_NAME(), minPrice); 37 | assertUnverifiedAddresses(1, 1, 0, unverified); 38 | assertVerifiedAddresses(1, 1, 2, verified); 39 | assertClusterNames(1, 1, 1, names); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/unit/remote/concrete/outbound/Endpoint_gasAirdrop.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Inbound_Harberger_Shared_Test, console2} from "../../shared/SharedInboundHarbergerTest.t.sol"; 5 | import {OptionsBuilder} from "layerzero-oapp/contracts/oapp/libs/OptionsBuilder.sol"; 6 | 7 | // TODO: Convert Inbound shared test to Outbound when Outbound infra is ready 8 | contract Outbound_Endpoint_gasAirdrop_Unit_Concrete_Test is Inbound_Harberger_Shared_Test { 9 | using OptionsBuilder for bytes; 10 | 11 | function testGasAirdrop() public { 12 | vm.startPrank(users.alicePrimary); 13 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(50_000, 0) 14 | .addExecutorNativeDropOption(uint128(minPrice), _addressToBytes32(users.aliceSecondary)); 15 | uint256 balance = address(users.aliceSecondary).balance; 16 | (uint256 nativeFee,) = localEndpoint.quote(2, bytes(""), options, false); 17 | localEndpoint.gasAirdrop{value: nativeFee}(nativeFee, 2, options); 18 | //verifyPackets(2, address(remoteEndpoint)); 19 | vm.stopPrank(); 20 | assertEq(balance + minPrice, address(users.aliceSecondary).balance, "airdrop balance error"); 21 | } 22 | 23 | function testGasAirdropInMulticall() public { 24 | vm.startPrank(users.alicePrimary); 25 | bytes32 caller = _addressToBytes32(users.alicePrimary); 26 | string memory testName = constants.TEST_NAME(); 27 | bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(50_000, 0) 28 | .addExecutorNativeDropOption(uint128(minPrice), _addressToBytes32(users.aliceSecondary)); 29 | (uint256 airdropFee,) = localEndpoint.quote(2, bytes(""), options, false); 30 | uint256 balance = address(users.aliceSecondary).balance; 31 | bytes[] memory data = new bytes[](2); 32 | data[0] = abi.encodeWithSignature("buyName(bytes32,uint256,string)", caller, minPrice, testName); 33 | data[1] = abi.encodeWithSignature("gasAirdrop(uint256,uint32,bytes)", airdropFee, 2, options); 34 | vm.stopPrank(); 35 | 36 | vm.startPrank(users.signer); 37 | bytes32 messageHash = localEndpoint.getMulticallHash(data); 38 | bytes32 digest = localEndpoint.getEthSignedMessageHash(messageHash); 39 | (uint8 v, bytes32 r, bytes32 s) = vm.sign(users.signerPrivKey, digest); 40 | bytes memory sig = abi.encodePacked(r, s, v); 41 | vm.stopPrank(); 42 | 43 | vm.prank(users.alicePrimary); 44 | console2.logBytes(options); 45 | localEndpoint.multicall{value: airdropFee + minPrice}(data, sig); 46 | //verifyPackets(2, address(remoteEndpoint)); 47 | assertEq(balance + minPrice, address(users.aliceSecondary).balance, "airdrop balance error"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /test/unit/remote/shared/SharedInboundHarbergerTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | import {Base_Test, ClustersHub, Endpoint, OAppUpgradeable, EnumerableSet, console2} from "../../../Base.t.sol"; 5 | //import {@layerzerolabs/lz-evm-protocol-v2/ 6 | 7 | abstract contract Inbound_Harberger_Shared_Test is Base_Test { 8 | using EnumerableSet for EnumerableSet.AddressSet; 9 | 10 | Endpoint internal localEndpoint; 11 | Endpoint internal remoteEndpoint; 12 | ClustersHub internal localClusters; 13 | 14 | function setUp() public virtual override { 15 | Base_Test.setUp(); 16 | configureHarbergerEnvironment(); 17 | localEndpoint = Endpoint(endpointGroup.at(0)); 18 | remoteEndpoint = Endpoint(endpointGroup.at(1)); 19 | localClusters = ClustersHub(clustersGroup.at(0)); 20 | console2.log("localClusters address:"); 21 | console2.log(address(localClusters)); 22 | console2.log(""); 23 | console2.log("localEndpoint address:"); 24 | console2.log(address(localEndpoint)); 25 | console2.log("localEndpoint's LZ Endpoint:"); 26 | console2.log(address(OAppUpgradeable(localEndpoint).endpoint())); 27 | console2.log("localEndpoint's LZ EID:"); 28 | console2.log((OAppUpgradeable(localEndpoint).endpoint()).eid()); 29 | console2.log("localEndpoint's DstEid: (will be 0 as it doesn't send to a destination yet)"); 30 | console2.log(localEndpoint.dstEid()); 31 | console2.log(""); 32 | console2.log("remoteEndpoint address:"); 33 | console2.log(address(remoteEndpoint)); 34 | console2.log("remoteEndpoint's LZ Endpoint:"); 35 | console2.log(address(OAppUpgradeable(remoteEndpoint).endpoint())); 36 | console2.log("remoteEndpoint's LZ EID:"); 37 | console2.log((OAppUpgradeable(remoteEndpoint).endpoint()).eid()); 38 | console2.log("remoteEndpoint's DstEid:"); 39 | console2.log(remoteEndpoint.dstEid()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/utils/Constants.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | contract Constants { 5 | uint256 public constant START_TIME = 1702620000; // 15 DEC 2023 00:00 UTC 6 | uint256 public constant USERS_FUNDING_AMOUNT = 10 ether; 7 | uint256 public constant MARKET_OPEN_TIMESTAMP = START_TIME + 7 days; 8 | uint256 public constant ECDSA_LIMIT = 115792089237316195423570985008687907852837564279074904382605163141518161494337; 9 | string public constant TEST_NAME = "foobar"; 10 | address public constant TEST_ADDRESS = address(13); 11 | } 12 | -------------------------------------------------------------------------------- /test/utils/Types.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | struct Users { 5 | // Signer's private key 6 | uint256 signerPrivKey; 7 | // Frontend's signer 8 | address payable signer; 9 | // Default admin for Clusters 10 | address payable clustersAdmin; 11 | // Alice's primary address 12 | address payable alicePrimary; 13 | // Alice's secondary address 14 | address payable aliceSecondary; 15 | // Bob's primary address 16 | address payable bobPrimary; 17 | // Bob's secondary address 18 | address payable bobSecondary; 19 | // Bidder 20 | address payable bidder; 21 | // Malicious user 22 | address payable hacker; 23 | } 24 | -------------------------------------------------------------------------------- /test/utils/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.23; 3 | 4 | abstract contract Utils { 5 | /// @dev Convert string to bytes32 6 | function _stringToBytes32(string memory name) internal pure returns (bytes32) { 7 | bytes memory stringBytes = bytes(name); 8 | return bytes32(stringBytes); 9 | } 10 | 11 | /// @dev Convert bytes32 to string 12 | function _bytes32ToString(bytes32 input) internal pure returns (string memory result) { 13 | if (input == bytes32("")) return result; 14 | /// @solidity memory-safe-assembly 15 | assembly { 16 | result := mload(0x40) 17 | let n 18 | for {} 1 {} { 19 | n := add(n, 1) 20 | if iszero(byte(n, input)) { break } // Scan for '\0'. 21 | } 22 | mstore(result, n) 23 | let o := add(result, 0x20) 24 | mstore(o, input) 25 | mstore(add(o, n), 0) 26 | mstore(0x40, add(result, 0x40)) 27 | } 28 | } 29 | 30 | /// @dev Convert address to bytes32 31 | function _addressToBytes32(address addr) internal pure returns (bytes32) { 32 | return bytes32(uint256(uint160(addr))); 33 | } 34 | 35 | /// @dev Convert bytes32 to address 36 | function _bytes32ToAddress(bytes32 input) internal pure returns (address) { 37 | return address(uint160(uint256(input))); 38 | } 39 | } 40 | --------------------------------------------------------------------------------