├── .github └── workflows │ ├── evm.yml │ ├── solana.yml │ └── universal-rs.yml ├── .gitignore ├── .gitmodules ├── .prettierrc.json ├── LICENSE ├── Makefile ├── README.md ├── audits ├── 2024-06-07-sec3-composable-intents.pdf ├── 2024-06-25-ottersec-composable-intents.pdf └── 2024-09-12-sec3-composable-intents-incremental.pdf ├── evm ├── .gitignore ├── Makefile ├── README.md ├── cfg │ └── deployment.testnet.json.sample ├── env │ ├── localnet │ │ ├── Avalanche.env │ │ └── Ethereum.env │ ├── testing.env │ └── testnet │ │ ├── ArbitrumSepolia.env │ │ ├── Avalanche.env │ │ ├── BaseSepolia.env │ │ ├── OptimismSepolia.env │ │ ├── PolygonSepolia.env │ │ └── Sepolia.env ├── forge │ ├── modules │ │ ├── circle │ │ │ ├── CircleSimulator.sol │ │ │ └── IUSDC.sol │ │ └── wormhole │ │ │ ├── BytesLib.sol │ │ │ ├── ICircleIntegration.sol │ │ │ ├── IWETH.sol │ │ │ ├── MockWormhole.sol │ │ │ └── WormholeSimulator.sol │ ├── scripts │ │ ├── DeployMatchingEngineContract.s.sol │ │ ├── DeployTokenRouterContracts.s.sol │ │ ├── TestTransfer.s.sol │ │ ├── UpgradeMatchingEngine.s.sol │ │ ├── UpgradeTokenRouter.s.sol │ │ └── helpers │ │ │ └── CheckWormholeContracts.sol │ └── tests │ │ ├── MatchingEngine.t.sol │ │ ├── TokenRouter.t.sol │ │ └── helpers │ │ └── mock │ │ ├── MockMatchingEngineImplementation.sol │ │ └── MockTokenRouterImplementation.sol ├── foundry.toml ├── package.json ├── sh │ ├── configure_matching_engine.sh │ ├── configure_token_router.sh │ ├── deploy_matching_engine.sh │ ├── deploy_token_router.sh │ ├── test_transfer.sh │ ├── update_auction_config.sh │ ├── update_fast_transfer_parameters.sh │ ├── upgrade_matching_engine.sh │ └── upgrade_token_router.sh ├── src │ ├── MatchingEngine │ │ ├── MatchingEngine.sol │ │ └── assets │ │ │ ├── Errors.sol │ │ │ ├── MatchingEngineAdmin.sol │ │ │ ├── MatchingEngineFastOrders.sol │ │ │ ├── State.sol │ │ │ └── Storage.sol │ ├── TokenRouter │ │ ├── TokenRouter.sol │ │ └── assets │ │ │ ├── Errors.sol │ │ │ ├── PlaceMarketOrder.sol │ │ │ ├── RedeemFill.sol │ │ │ ├── State.sol │ │ │ ├── Storage.sol │ │ │ └── TokenRouterAdmin.sol │ ├── interfaces │ │ ├── IAdmin.sol │ │ ├── IMatchingEngine.sol │ │ ├── IMatchingEngineAdmin.sol │ │ ├── IMatchingEngineFastOrders.sol │ │ ├── IMatchingEngineState.sol │ │ ├── IMatchingEngineTypes.sol │ │ ├── IPlaceMarketOrder.sol │ │ ├── IRedeemFill.sol │ │ ├── ITokenRouter.sol │ │ ├── ITokenRouterAdmin.sol │ │ ├── ITokenRouterEvents.sol │ │ ├── ITokenRouterState.sol │ │ ├── ITokenRouterTypes.sol │ │ └── external │ │ │ ├── ICircleBridge.sol │ │ │ ├── IMessageTransmitter.sol │ │ │ ├── ITokenMessenger.sol │ │ │ └── ITokenMinter.sol │ └── shared │ │ ├── Admin.sol │ │ ├── Implementation.sol │ │ ├── Messages.sol │ │ ├── Utils.sol │ │ ├── WormholeCctpMessages.sol │ │ ├── WormholeCctpTokenMessenger.sol │ │ └── external │ │ └── Initializable.sol ├── ts │ ├── scripts │ │ ├── helpers │ │ │ ├── consts.ts │ │ │ ├── helpers.ts │ │ │ └── index.ts │ │ ├── set_fast_transfer_parameters.ts │ │ ├── setup_matching_engine.ts │ │ └── setup_token_router.ts │ ├── src │ │ ├── MatchingEngine │ │ │ ├── evm.ts │ │ │ └── index.ts │ │ ├── TokenRouter │ │ │ ├── evm.ts │ │ │ └── index.ts │ │ ├── error.ts │ │ ├── index.ts │ │ ├── messages.ts │ │ ├── testing │ │ │ ├── consts.ts │ │ │ ├── env.ts │ │ │ ├── index.ts │ │ │ ├── mock │ │ │ │ ├── circleAttester.ts │ │ │ │ ├── index.ts │ │ │ │ └── wormhole.ts │ │ │ └── utils.ts │ │ └── utils.ts │ └── tests │ │ ├── 00__environment.ts │ │ ├── 01__registration.ts │ │ ├── 02__configuration.ts │ │ ├── 03__marketOrder.ts │ │ ├── run_integration_test.sh │ │ └── tsconfig.json ├── tsconfig.cjs.json ├── tsconfig.esm.json └── tsconfig.json ├── package-lock.json ├── package.json ├── solana ├── .gitignore ├── Anchor.toml ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── README.md ├── clippy.toml ├── modules │ ├── common │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── admin │ │ │ ├── mod.rs │ │ │ └── utils │ │ │ │ ├── assistant.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── ownable.rs │ │ │ │ ├── pending_owner.rs │ │ │ │ └── upgrade.rs │ │ │ └── lib.rs │ └── token-router-sdk │ │ ├── Cargo.toml │ │ └── src │ │ ├── accounts │ │ ├── mod.rs │ │ └── prepared_fill.rs │ │ └── lib.rs ├── package.json ├── programs │ ├── matching-engine │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── Xargo.toml │ │ └── src │ │ │ ├── composite │ │ │ └── mod.rs │ │ │ ├── error.rs │ │ │ ├── events │ │ │ ├── auction_closed.rs │ │ │ ├── auction_settled.rs │ │ │ ├── auction_updated.rs │ │ │ ├── enacted.rs │ │ │ ├── fast_fill_redeemed.rs │ │ │ ├── fast_fill_sequence_reserved.rs │ │ │ ├── filled_local_fast_order.rs │ │ │ ├── mod.rs │ │ │ ├── order_executed.rs │ │ │ └── proposed.rs │ │ │ ├── lib.rs │ │ │ ├── processor │ │ │ ├── admin │ │ │ │ ├── close_proposal.rs │ │ │ │ ├── init_event.rs │ │ │ │ ├── initialize.rs │ │ │ │ ├── migrate.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── ownership_transfer_request │ │ │ │ │ ├── cancel.rs │ │ │ │ │ ├── confirm.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── submit.rs │ │ │ │ ├── propose │ │ │ │ │ ├── auction_parameters.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── router_endpoint │ │ │ │ │ ├── add │ │ │ │ │ │ ├── cctp.rs │ │ │ │ │ │ ├── local.rs │ │ │ │ │ │ └── mod.rs │ │ │ │ │ ├── disable.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── update │ │ │ │ │ │ ├── cctp.rs │ │ │ │ │ │ ├── local.rs │ │ │ │ │ │ └── mod.rs │ │ │ │ ├── set_pause.rs │ │ │ │ └── update │ │ │ │ │ ├── auction_parameters.rs │ │ │ │ │ ├── fee_recipient_token.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── owner_assistant.rs │ │ │ ├── auction │ │ │ │ ├── close.rs │ │ │ │ ├── execute_fast_order │ │ │ │ │ ├── cctp.rs │ │ │ │ │ ├── local.rs │ │ │ │ │ └── mod.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── offer │ │ │ │ │ ├── improve.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── place_initial │ │ │ │ │ │ ├── cctp.rs │ │ │ │ │ │ └── mod.rs │ │ │ │ ├── prepare_order_response │ │ │ │ │ ├── cctp.rs │ │ │ │ │ └── mod.rs │ │ │ │ └── settle │ │ │ │ │ ├── complete.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── none │ │ │ │ │ ├── cctp.rs │ │ │ │ │ ├── local.rs │ │ │ │ │ └── mod.rs │ │ │ ├── fast_fill │ │ │ │ ├── close_redeemed.rs │ │ │ │ ├── complete.rs │ │ │ │ ├── mod.rs │ │ │ │ └── reserve_sequence │ │ │ │ │ ├── active_auction.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── no_auction.rs │ │ │ └── mod.rs │ │ │ ├── state │ │ │ ├── auction.rs │ │ │ ├── auction_config.rs │ │ │ ├── auction_history.rs │ │ │ ├── custodian.rs │ │ │ ├── fast_fill │ │ │ │ ├── mod.rs │ │ │ │ ├── reserved_sequence.rs │ │ │ │ └── sequencer.rs │ │ │ ├── mod.rs │ │ │ ├── prepared_order_response.rs │ │ │ ├── proposal.rs │ │ │ └── router_endpoint.rs │ │ │ └── utils │ │ │ ├── admin.rs │ │ │ ├── auction.rs │ │ │ └── mod.rs │ ├── token-router │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ │ ├── composite │ │ │ └── mod.rs │ │ │ ├── error.rs │ │ │ ├── lib.rs │ │ │ ├── processor │ │ │ ├── admin │ │ │ │ ├── initialize.rs │ │ │ │ ├── migrate.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── ownership_transfer_request │ │ │ │ │ ├── cancel.rs │ │ │ │ │ ├── confirm.rs │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── submit.rs │ │ │ │ ├── set_pause.rs │ │ │ │ └── update │ │ │ │ │ ├── mod.rs │ │ │ │ │ └── owner_assistant.rs │ │ │ ├── close_prepared_order.rs │ │ │ ├── consume_prepared_fill.rs │ │ │ ├── market_order │ │ │ │ ├── mod.rs │ │ │ │ ├── place_cctp.rs │ │ │ │ └── prepare.rs │ │ │ ├── mod.rs │ │ │ └── redeem_fill │ │ │ │ ├── cctp.rs │ │ │ │ ├── fast.rs │ │ │ │ └── mod.rs │ │ │ └── state │ │ │ ├── custodian.rs │ │ │ ├── mod.rs │ │ │ ├── prepared_fill.rs │ │ │ └── prepared_order.rs │ └── upgrade-manager │ │ ├── Cargo.toml │ │ ├── README.md │ │ └── src │ │ ├── composite │ │ └── mod.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── processor │ │ ├── matching_engine_upgrade │ │ │ ├── commit.rs │ │ │ ├── execute.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ └── token_router_upgrade │ │ │ ├── commit.rs │ │ │ ├── execute.rs │ │ │ └── mod.rs │ │ ├── state │ │ ├── mod.rs │ │ └── upgrade_receipt.rs │ │ └── utils │ │ └── mod.rs ├── rust-toolchain.toml ├── sh │ ├── kill_test_validator.sh │ ├── run_anchor_test.sh │ ├── run_anchor_test_upgrade.sh │ └── run_test_validator.sh ├── ts │ ├── scripts │ │ ├── dedupeVaaExample.ts │ │ ├── enactNewTestnetAuctionConfig.ts │ │ ├── executeFastOrder.ts │ │ ├── getMainnetInfo.ts │ │ ├── getTestnetInfo.ts │ │ ├── setUpMatchingEngineLut.ts │ │ ├── setUpTestnetMatchingEngine.ts │ │ ├── setUpTestnetTokenRouter.ts │ │ └── upgradeTestnet.ts │ ├── src │ │ ├── cctp │ │ │ ├── index.ts │ │ │ ├── messageTransmitter │ │ │ │ ├── MessageSent.ts │ │ │ │ ├── MessageTransmitterConfig.ts │ │ │ │ ├── UsedNonces.ts │ │ │ │ └── index.ts │ │ │ ├── messages.ts │ │ │ ├── tokenMessengerMinter │ │ │ │ ├── RemoteTokenMessenger.ts │ │ │ │ └── index.ts │ │ │ └── types │ │ │ │ ├── message_transmitter.ts │ │ │ │ └── token_messenger_minter.ts │ │ ├── common │ │ │ ├── index.ts │ │ │ ├── messages │ │ │ │ ├── deposit.ts │ │ │ │ └── index.ts │ │ │ └── state │ │ │ │ └── index.ts │ │ ├── idl │ │ │ ├── index.ts │ │ │ ├── json │ │ │ │ ├── matching_engine.json │ │ │ │ ├── token_router.json │ │ │ │ └── upgrade_manager.json │ │ │ └── ts │ │ │ │ ├── matching_engine.ts │ │ │ │ ├── token_router.ts │ │ │ │ └── upgrade_manager.ts │ │ ├── index.ts │ │ ├── matchingEngine │ │ │ ├── index.ts │ │ │ └── state │ │ │ │ ├── Auction.ts │ │ │ │ ├── AuctionConfig.ts │ │ │ │ ├── AuctionHistory.ts │ │ │ │ ├── Custodian.ts │ │ │ │ ├── FastFill.ts │ │ │ │ ├── FastFillSequencer.ts │ │ │ │ ├── PreparedOrderResponse.ts │ │ │ │ ├── Proposal.ts │ │ │ │ ├── ReservedFastFillSequence.ts │ │ │ │ ├── RouterEndpoint.ts │ │ │ │ └── index.ts │ │ ├── testing │ │ │ ├── consts.ts │ │ │ ├── index.ts │ │ │ ├── mock.ts │ │ │ └── utils.ts │ │ ├── tokenRouter │ │ │ ├── index.ts │ │ │ └── state │ │ │ │ ├── Custodian.ts │ │ │ │ ├── PreparedFill.ts │ │ │ │ ├── PreparedOrder.ts │ │ │ │ └── index.ts │ │ ├── upgradeManager │ │ │ ├── index.ts │ │ │ └── state │ │ │ │ ├── UpgradeReceipt.ts │ │ │ │ └── index.ts │ │ ├── utils.ts │ │ └── wormhole │ │ │ ├── index.ts │ │ │ └── spy.ts │ └── tests │ │ ├── 01__matchingEngine.ts │ │ ├── 02__tokenRouter.ts │ │ ├── 04__interaction.ts │ │ ├── 12__testnetFork.ts │ │ ├── accounts │ │ ├── core_bridge │ │ │ ├── config.json │ │ │ ├── fee_collector.json │ │ │ └── guardian_set_0.json │ │ ├── message_transmitter │ │ │ └── message_transmitter_config.json │ │ ├── testnet │ │ │ ├── matching_engine_custodian.json │ │ │ ├── token_router_custodian.json │ │ │ └── token_router_program_data_hacked.json │ │ ├── token_messenger_minter │ │ │ ├── arbitrum_remote_token_messenger.json │ │ │ ├── ethereum_remote_token_messenger.json │ │ │ ├── misconfigured_remote_token_messenger.json │ │ │ ├── token_messenger.json │ │ │ ├── token_minter.json │ │ │ ├── usdc_custody_token.json │ │ │ ├── usdc_local_token.json │ │ │ └── usdc_token_pair.json │ │ ├── usdc_mint.json │ │ └── usdc_payer_token.json │ │ └── keys │ │ └── pFCBP4bhqdSsrWUVTgqhPsLrfEdChBK17vgFM7TxjxQ.json ├── tsconfig.anchor.json ├── tsconfig.cjs.json ├── tsconfig.esm.json └── tsconfig.json ├── solver ├── .gitignore ├── Makefile ├── README.md ├── cfg │ └── testnet │ │ └── sample.config.json ├── docker-compose.yml ├── package.json ├── src │ ├── containers │ │ ├── CachedBlockhash.ts │ │ ├── OfferToken.ts │ │ └── index.ts │ ├── executeOrder │ │ └── app.ts │ ├── improveOffer │ │ └── app.ts │ ├── utils │ │ ├── config.ts │ │ ├── evm │ │ │ ├── index.ts │ │ │ └── wormholeCctp.ts │ │ ├── index.ts │ │ ├── logger.ts │ │ ├── placeInitialOffer.ts │ │ ├── preparePostVaaTx.ts │ │ ├── sendTx.ts │ │ ├── settleAuction.ts │ │ └── wormscan.ts │ └── vaaAuctionRelayer │ │ └── app.ts └── tsconfig.json ├── tsconfig.json └── universal ├── rs ├── .gitignore ├── Cargo.toml ├── messages │ ├── Cargo.toml │ └── src │ │ ├── deposit │ │ ├── fill.rs │ │ ├── mod.rs │ │ └── slow_order_response.rs │ │ ├── fast_market_order.rs │ │ ├── lib.rs │ │ └── raw │ │ ├── deposit.rs │ │ └── mod.rs └── rust-toolchain.toml └── ts ├── package.json ├── src ├── index.ts ├── messages.ts └── payloads.ts ├── tests └── layouts.ts ├── tsconfig.cjs.json ├── tsconfig.esm.json └── tsconfig.json /.github/workflows/evm.yml: -------------------------------------------------------------------------------- 1 | name: EVM CI 2 | on: 3 | workflow_dispatch: 4 | pull_request: 5 | paths: 6 | - 'evm/**' 7 | push: 8 | branches: 9 | - main 10 | env: 11 | FOUNDRY_PROFILE: ci 12 | jobs: 13 | test: 14 | strategy: 15 | fail-fast: true 16 | name: forge test 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | submodules: recursive 22 | - name: Install Foundry 23 | uses: foundry-rs/foundry-toolchain@v1 24 | with: 25 | version: nightly 26 | - name: Run Forge build 27 | working-directory: evm 28 | run: make build 29 | - name: Run unit tests 30 | working-directory: evm 31 | run: make unit-test 32 | - name: Run Integration tests 33 | working-directory: evm 34 | run: make integration-test -------------------------------------------------------------------------------- /.github/workflows/universal-rs.yml: -------------------------------------------------------------------------------- 1 | name: universal-rs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | paths: 9 | - 'universal/**' 10 | 11 | env: 12 | CARGO_TERM_COLOR: always 13 | RUSTC_VERSION: 1.75.0 14 | 15 | jobs: 16 | test: 17 | name: test 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 30 20 | strategy: 21 | fail-fast: false 22 | steps: 23 | - uses: actions/checkout@v4 24 | - name: Install toolchain 25 | uses: dtolnay/rust-toolchain@master 26 | with: 27 | toolchain: ${{ env.RUSTC_VERSION }} 28 | - name: test 29 | run: cargo test --workspace --all-features 30 | working-directory: ./universal/rs 31 | 32 | clippy: 33 | name: clippy 34 | runs-on: ubuntu-latest 35 | timeout-minutes: 30 36 | steps: 37 | - uses: actions/checkout@v4 38 | - name: Install toolchain 39 | uses: dtolnay/rust-toolchain@master 40 | with: 41 | toolchain: ${{ env.RUSTC_VERSION }} 42 | components: clippy 43 | - run: cargo clippy --workspace --all-targets 44 | working-directory: ./universal/rs 45 | env: 46 | RUSTFLAGS: -Dwarnings 47 | 48 | docs: 49 | name: docs 50 | runs-on: ubuntu-latest 51 | timeout-minutes: 30 52 | steps: 53 | - uses: actions/checkout@v4 54 | - name: Install toolchain 55 | uses: dtolnay/rust-toolchain@master 56 | with: 57 | toolchain: ${{ env.RUSTC_VERSION }} 58 | components: rust-docs 59 | - run: cargo doc --workspace --no-deps --document-private-items 60 | working-directory: ./universal/rs 61 | env: 62 | RUSTDOCFLAGS: "--cfg docsrs -D warnings" 63 | 64 | fmt: 65 | name: fmt 66 | runs-on: ubuntu-latest 67 | timeout-minutes: 30 68 | steps: 69 | - uses: actions/checkout@v4 70 | - name: Install toolchain 71 | uses: dtolnay/rust-toolchain@master 72 | with: 73 | toolchain: ${{ env.RUSTC_VERSION }} 74 | components: rustfmt 75 | - run: cargo fmt --all --check 76 | working-directory: ./universal/rs -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .private 4 | .vscode 5 | node_modules 6 | *.tgz 7 | dist 8 | .env 9 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "evm/lib/forge-std"] 2 | path = evm/lib/forge-std 3 | url = https://github.com/foundry-rs/forge-std 4 | branch = v1.8.0 5 | [submodule "evm/lib/openzeppelin-contracts"] 6 | path = evm/lib/openzeppelin-contracts 7 | url = https://github.com/openzeppelin/openzeppelin-contracts 8 | branch = v4.9.6 9 | [submodule "evm/lib/wormhole-solidity-sdk"] 10 | path = evm/lib/wormhole-solidity-sdk 11 | url = https://github.com/wormhole-foundation/wormhole-solidity-sdk 12 | branch = 2b7db51f99b49eda99b44f4a044e751cb0b2e8ea 13 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.ts", 5 | "options": { 6 | "printWidth": 100, 7 | "tabWidth": 4, 8 | "useTabs": false, 9 | "singleQuote": false, 10 | "trailingComma": "all", 11 | "bracketSpacing": true 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | .PHONY: clean 3 | clean: 4 | rm -rf node_modules 5 | npm run clean 6 | cd evm && $(MAKE) clean 7 | cd solana && $(MAKE) clean 8 | cd universal/rs && cargo clean 9 | 10 | .PHONY: clean-install 11 | clean-install: clean node_modules 12 | 13 | node_modules: 14 | npm ci 15 | 16 | .PHONY: build 17 | build: node_modules 18 | npm run build:universal -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # example-liquidity-layer 2 | 3 | The Example Liquidity Layer utilizes the [Wormhole Circle Integration]() contract to faciliate cross-chain transfers of USDC (along with arbitrary messages) to custom smart contracts on any CCTP-enabled blockchain. 4 | 5 | ## Get Started 6 | 7 | Clone the repo using the following command to make sure that all necessary submodules are installed: 8 | 9 | ``` 10 | git clone git@github.com:wormhole-foundation/example-liquidity-layer.git --recurse-submodules 11 | ``` 12 | 13 | ## Prerequisites 14 | 15 | ### EVM 16 | 17 | Install [Foundry tools](https://book.getfoundry.sh/getting-started/installation), which include `forge`, `anvil` and `cast` CLI tools. 18 | 19 | ## Build, Test and Deploy Smart Contracts 20 | 21 | Each directory represents Wormhole integrations for specific blockchain networks. Please navigate to a network subdirectory to see more details (see the relevant README.md) on building, testing and deploying the smart contracts. 22 | 23 | [Wormhole Circle Integration]: https://github.com/wormhole-foundation/wormhole-circle-integration/blob/main/DESIGN.md 24 | 25 | 26 | ### Typescript SDK 27 | 28 | To use the Typescript SDK, at the root of this repository, run: 29 | 30 | ```sh 31 | npm ci && npm run build && npm run pack 32 | ``` 33 | 34 | Which will produce a `.tgz` file that can be installed using npm or any other package manager like: 35 | 36 | ```sh 37 | npm install /path/to/example-liquidity-layer/wormhole-foundation-example-liquidity-layer-solana-0.0.1.tgz 38 | ``` 39 | 40 | Once installed, it can be used like any other package: 41 | 42 | ```ts 43 | // ... 44 | import * as tokenRouterSdk from "@wormhole-foundation/example-liquidity-layer-solana/tokenRouter"; 45 | import { 46 | LiquidityLayerDeposit, 47 | LiquidityLayerMessage, 48 | } from "@wormhole-foundation/example-liquidity-layer-solana/common"; 49 | import { PreparedOrder } from "@wormhole-foundation/example-liquidity-layer-solana/tokenRouter/state"; 50 | // ... 51 | ``` 52 | 53 | -------------------------------------------------------------------------------- /audits/2024-06-07-sec3-composable-intents.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wormhole-foundation/example-liquidity-layer/3879b3c8d884beb936b5bfb9ac5e34dd77d86105/audits/2024-06-07-sec3-composable-intents.pdf -------------------------------------------------------------------------------- /audits/2024-06-25-ottersec-composable-intents.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wormhole-foundation/example-liquidity-layer/3879b3c8d884beb936b5bfb9ac5e34dd77d86105/audits/2024-06-25-ottersec-composable-intents.pdf -------------------------------------------------------------------------------- /audits/2024-09-12-sec3-composable-intents-incremental.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wormhole-foundation/example-liquidity-layer/3879b3c8d884beb936b5bfb9ac5e34dd77d86105/audits/2024-09-12-sec3-composable-intents-incremental.pdf -------------------------------------------------------------------------------- /evm/.gitignore: -------------------------------------------------------------------------------- 1 | .anvil 2 | .env 3 | .vscode 4 | cache 5 | /bin 6 | node_modules 7 | out 8 | out.log 9 | broadcast 10 | forge-scripts/deploy.out 11 | ts/lib 12 | ts/src/types 13 | dist 14 | ts-types 15 | *.tsbuildinfo -------------------------------------------------------------------------------- /evm/Makefile: -------------------------------------------------------------------------------- 1 | include env/testing.env 2 | 3 | .PHONY: all unit-test integration-test test build dependencies clean 4 | 5 | all: build 6 | 7 | .PHONY: unit-test 8 | unit-test: build 9 | forge test --fork-url ${AVALANCHE_RPC} -vv 10 | 11 | .PHONY: integration-test 12 | integration-test: build ts/tests/.env 13 | bash ts/tests/run_integration_test.sh 14 | 15 | .PHONY: test 16 | test: forge-test ts/tests/.env 17 | bash ts/tests/run_integration_test.sh 18 | 19 | .PHONY: ts/tests/.env 20 | ts/tests/.env: 21 | cp env/testing.env ts/tests/.env 22 | 23 | .PHONY: forge-test 24 | forge-test: dependencies 25 | forge test --fork-url ${AVALANCHE_RPC} -vv 26 | 27 | .PHONY: build 28 | build: dependencies 29 | forge build 30 | npm run generate 31 | 32 | .PHONY: dependencies 33 | dependencies: node_modules 34 | 35 | .PHONY: clean 36 | clean: 37 | forge clean 38 | rm -rf anvil.log ts/src/types 39 | 40 | node_modules: ../package-lock.json 41 | cd .. && npm ci && npm run build:universal -------------------------------------------------------------------------------- /evm/cfg/deployment.testnet.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "matchingEngine": { 3 | "chain": 6, 4 | "address": "0x" 5 | }, 6 | "routers": { 7 | "6": { 8 | "address": "0x", 9 | "mintRecipient": "0x", 10 | "domain": "1" 11 | }, 12 | "10002": { 13 | "address": "0x", 14 | "mintRecipient": "0x", 15 | "domain": "0" 16 | }, 17 | "10003": { 18 | "address": "0x", 19 | "mintRecipient": "0x", 20 | "domain": "3" 21 | } 22 | }, 23 | "fastTransferParameters": { 24 | "enabled": true, 25 | "maxAmount": 5000000000, 26 | "baseFee": 50000, 27 | "initAuctionFee": 250000 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /evm/env/localnet/Ethereum.env: -------------------------------------------------------------------------------- 1 | export RELEASE_CHAIN_TYPE=evm 2 | 3 | ### Wormhole Chain ID (uint16) 4 | export RELEASE_CHAIN_ID=2 5 | 6 | ### Circle Domain (uint32) 7 | export RELEASE_DOMAIN=0 8 | 9 | ### Token (evm address) 10 | ### 11 | ### Token Info: https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 12 | export RELEASE_TOKEN_ADDRESS=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 13 | 14 | 15 | ############################# Wormhole Contracts ############################# 16 | 17 | 18 | ### Wormhole (evm address) 19 | export RELEASE_WORMHOLE_ADDRESS=0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B 20 | 21 | 22 | ############################# Circle Contracts ################################# 23 | 24 | ### Circle Token Messenger (evm address) 25 | export RELEASE_TOKEN_MESSENGER_ADDRESS=0xbd3fa81b58ba92a82136038b25adec7066af3155 26 | 27 | 28 | ################################# Ownership ################################## 29 | 30 | 31 | ### Owner Assistant (evm address) 32 | export RELEASE_OWNER_ASSISTANT_ADDRESS=0xACa94ef8bD5ffEE41947b4585a84BdA5a3d3DA6E 33 | 34 | 35 | ############################### Token Router ################################# 36 | 37 | 38 | ### Token Router Proxy (evm address) 39 | export RELEASE_TOKEN_ROUTER_ADDRESS=0xEeB3A6143B71b9eBc867479f2cf57DB0bE4604C2 40 | 41 | 42 | ############################### Matching Engine ############################### 43 | 44 | 45 | ### Matching Engine Proxy (universal evm address) 46 | export RELEASE_MATCHING_ENGINE_CHAIN=6 47 | export RELEASE_MATCHING_ENGINE_ADDRESS=0x00000000000000000000000027D44c7337ce4D67b7cd573e9c36bDEED2b2162a 48 | export RELEASE_MATCHING_ENGINE_MINT_RECIPIENT=0x00000000000000000000000027D44c7337ce4D67b7cd573e9c36bDEED2b2162a 49 | export RELEASE_MATCHING_ENGINE_DOMAIN=1 -------------------------------------------------------------------------------- /evm/env/testing.env: -------------------------------------------------------------------------------- 1 | export AVALANCHE_RPC=https://api.avax.network/ext/bc/C/rpc 2 | export ETHEREUM_RPC=https://eth.llamarpc.com 3 | 4 | 5 | export AVAX_USDC_ADDRESS=0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E 6 | export AVAX_MESSAGE_TRANSMITTER=0x8186359af5f57fbb40c6b14a588d2a59c0c29880 7 | export AVAX_CIRCLE_BRIDGE=0x6b25532e1060ce10cc3b0a99e5683b91bfde6982 8 | export AVAX_WORMHOLE=0x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c 9 | 10 | ### Devnet (tilt) guardian key 11 | export TESTING_DEVNET_GUARDIAN=cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0 12 | 13 | ### Mainnet token bridge addresses 14 | export SUI_TOKEN_BRIDGE_ADDRESS=0xccceeb29348f71bdd22ffef43a2a19c1f5b5e17c5cca5411529120182672ade5 15 | export ARB_TOKEN_BRIDGE_ADDRESS=0x0b2402144Bb366A632D14B83F244D2e0e21bD39c 16 | export POLY_TOKEN_BRIDGE_ADDRESS=0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE 17 | export ARB_CIRCLE_BRIDGE=0x19330d10D9Cc8751218eaf51E8885D058642E08A 18 | export ARB_CIRCLE_INTEGRATION=0x2703483B1a5a7c577e8680de9Df8Be03c6f30e3c 19 | -------------------------------------------------------------------------------- /evm/env/testnet/ArbitrumSepolia.env: -------------------------------------------------------------------------------- 1 | export RELEASE_CHAIN_TYPE=evm 2 | 3 | ### RPC 4 | export RPC= 5 | 6 | ### Wormhole Chain ID (uint16) 7 | export RELEASE_CHAIN_ID=10003 8 | 9 | ### Circle Domain (uint32) 10 | export RELEASE_DOMAIN=3 11 | 12 | ### Token (evm address) 13 | ### 14 | ### Token Info: https://sepolia.arbiscan.io/address/0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d 15 | export RELEASE_TOKEN_ADDRESS=0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d 16 | 17 | ############################# Wormhole Contracts ############################# 18 | 19 | ### Wormhole (evm address) 20 | export RELEASE_WORMHOLE_ADDRESS=0x6b9C8671cdDC8dEab9c719bB87cBd3e782bA6a35 21 | 22 | 23 | ############################# Circle Contracts ################################# 24 | 25 | ### Circle Token Messenger (evm address) 26 | export RELEASE_TOKEN_MESSENGER_ADDRESS=0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5 27 | 28 | 29 | ################################# Ownership ################################## 30 | 31 | 32 | ### Owner Assistant (evm address) 33 | export RELEASE_OWNER_ASSISTANT_ADDRESS=0x 34 | 35 | 36 | ############################### Token Router ################################# 37 | 38 | 39 | ### Token Router Proxy (evm address) 40 | export RELEASE_TOKEN_ROUTER_ADDRESS=0xe0418C44F06B0b0D7D1706E01706316DBB0B210E 41 | 42 | 43 | ############################### Matching Engine ############################### 44 | 45 | 46 | ### Matching Engine Proxy (universal evm address) 47 | export RELEASE_MATCHING_ENGINE_CHAIN=1 48 | export RELEASE_MATCHING_ENGINE_ADDRESS=0x3e374fcd3aaf2ed067f3c93d21416855ec7916cfd2c2127bcbc68b3b1fb73077 49 | export RELEASE_MATCHING_ENGINE_MINT_RECIPIENT=0x58b82fca98f022ca29cfe822d1a3f92930020970910f0a99a0d9b995b21f99d5 50 | export RELEASE_MATCHING_ENGINE_DOMAIN=5 51 | 52 | 53 | ############################## Test Params ############################### 54 | 55 | 56 | export TEST_AMOUNT_IN=500000000 57 | export TEST_TARGET_CHAIN= 58 | export TEST_REDEEMER= 59 | export TEST_IS_FAST=true 60 | export TEST_FEE= 61 | export TEST_DEADLINE=0 62 | 63 | -------------------------------------------------------------------------------- /evm/env/testnet/BaseSepolia.env: -------------------------------------------------------------------------------- 1 | export RELEASE_CHAIN_TYPE=evm 2 | 3 | ### RPC 4 | export RPC=https://sepolia.base.org 5 | 6 | ### Wormhole Chain ID (uint16) 7 | export RELEASE_CHAIN_ID=10004 8 | 9 | ### Circle Domain (uint32) 10 | export RELEASE_DOMAIN=6 11 | 12 | ### Token (evm address) 13 | ### 14 | ### Token Info: https://base-sepolia.blockscout.com/address/0x036CbD53842c5426634e7929541eC2318f3dCF7e 15 | export RELEASE_TOKEN_ADDRESS=0x036CbD53842c5426634e7929541eC2318f3dCF7e 16 | 17 | ############################# Wormhole Contracts ############################# 18 | 19 | ### Wormhole (evm address) 20 | export RELEASE_WORMHOLE_ADDRESS=0x79A1027a6A159502049F10906D333EC57E95F083 21 | 22 | 23 | ############################# Circle Contracts ################################# 24 | 25 | ### Circle Token Messenger (evm address) 26 | export RELEASE_TOKEN_MESSENGER_ADDRESS=0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5 27 | 28 | 29 | ################################# Ownership ################################## 30 | 31 | 32 | ### Owner Assistant (evm address) 33 | export RELEASE_OWNER_ASSISTANT_ADDRESS=0x 34 | 35 | 36 | ############################### Token Router ################################# 37 | 38 | 39 | ### Token Router Proxy (evm address) 40 | export RELEASE_TOKEN_ROUTER_ADDRESS=0x824Ea687CD1CC2f2446235D33Ae764CbCd08e18C 41 | 42 | 43 | ############################### Matching Engine ############################### 44 | 45 | 46 | ### Matching Engine Proxy (universal evm address) 47 | export RELEASE_MATCHING_ENGINE_CHAIN=1 48 | export RELEASE_MATCHING_ENGINE_ADDRESS=0x3e374fcd3aaf2ed067f3c93d21416855ec7916cfd2c2127bcbc68b3b1fb73077 49 | export RELEASE_MATCHING_ENGINE_MINT_RECIPIENT=0x58b82fca98f022ca29cfe822d1a3f92930020970910f0a99a0d9b995b21f99d5 50 | export RELEASE_MATCHING_ENGINE_DOMAIN=5 51 | 52 | 53 | ############################## Test Params ############################### 54 | 55 | 56 | export TEST_AMOUNT_IN=500000000 57 | export TEST_TARGET_CHAIN= 58 | export TEST_REDEEMER= 59 | export TEST_IS_FAST=true 60 | export TEST_FEE= 61 | export TEST_DEADLINE=0 62 | -------------------------------------------------------------------------------- /evm/env/testnet/OptimismSepolia.env: -------------------------------------------------------------------------------- 1 | export RELEASE_CHAIN_TYPE=evm 2 | 3 | ### RPC 4 | export RPC= 5 | 6 | ### Wormhole Chain ID (uint16) 7 | export RELEASE_CHAIN_ID=10005 8 | 9 | ### Circle Domain (uint32) 10 | export RELEASE_DOMAIN=2 11 | 12 | ### Token (evm address) 13 | ### 14 | ### Token Info: https://sepolia-optimism.etherscan.io/address/0x5fd84259d66Cd46123540766Be93DFE6D43130D7 15 | export RELEASE_TOKEN_ADDRESS=0x5fd84259d66Cd46123540766Be93DFE6D43130D7 16 | 17 | ############################# Wormhole Contracts ############################# 18 | 19 | ### Wormhole (evm address) 20 | export RELEASE_WORMHOLE_ADDRESS=0x31377888146f3253211EFEf5c676D41ECe7D58Fe 21 | 22 | 23 | ############################# Circle Contracts ################################# 24 | 25 | ### Circle Token Messenger (evm address) 26 | export RELEASE_TOKEN_MESSENGER_ADDRESS=0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5 27 | 28 | 29 | ################################# Ownership ################################## 30 | 31 | 32 | ### Owner Assistant (evm address) 33 | export RELEASE_OWNER_ASSISTANT_ADDRESS=0x 34 | 35 | 36 | ############################### Token Router ################################# 37 | 38 | 39 | ### Token Router Proxy (evm address) 40 | export RELEASE_TOKEN_ROUTER_ADDRESS=0x6BAa7397c18abe6221b4f6C3Ac91C88a9faE00D8 41 | 42 | 43 | ############################### Matching Engine ############################### 44 | 45 | 46 | ### Matching Engine Proxy (universal evm address) 47 | export RELEASE_MATCHING_ENGINE_CHAIN=1 48 | export RELEASE_MATCHING_ENGINE_ADDRESS=0x3e374fcd3aaf2ed067f3c93d21416855ec7916cfd2c2127bcbc68b3b1fb73077 49 | export RELEASE_MATCHING_ENGINE_MINT_RECIPIENT=0x58b82fca98f022ca29cfe822d1a3f92930020970910f0a99a0d9b995b21f99d5 50 | export RELEASE_MATCHING_ENGINE_DOMAIN=5 51 | 52 | 53 | ############################## Test Params ############################### 54 | 55 | 56 | export TEST_AMOUNT_IN=500000000 57 | export TEST_TARGET_CHAIN= 58 | export TEST_REDEEMER= 59 | export TEST_IS_FAST=true 60 | export TEST_FEE= 61 | export TEST_DEADLINE=0 62 | -------------------------------------------------------------------------------- /evm/env/testnet/PolygonSepolia.env: -------------------------------------------------------------------------------- 1 | export RELEASE_CHAIN_TYPE=evm 2 | 3 | ### RPC 4 | export RPC=https://rpc-amoy.polygon.technology/ 5 | 6 | ### Wormhole Chain ID (uint16) 7 | export RELEASE_CHAIN_ID=10007 8 | 9 | ### Circle Domain (uint32) 10 | export RELEASE_DOMAIN=7 11 | 12 | ### Token (evm address) 13 | ### 14 | ### Token Info: https://www.oklink.com/amoy/address/0x41e94eb019c0762f9bfcf9fb1e58725bfb0e7582 15 | export RELEASE_TOKEN_ADDRESS=0x41E94Eb019C0762f9Bfcf9Fb1E58725BfB0e7582 16 | 17 | ############################# Wormhole Contracts ############################# 18 | 19 | ### Wormhole (evm address) 20 | export RELEASE_WORMHOLE_ADDRESS=0x6b9C8671cdDC8dEab9c719bB87cBd3e782bA6a35 21 | 22 | 23 | ############################# Circle Contracts ################################# 24 | 25 | ### Circle Token Messenger (evm address) 26 | export RELEASE_TOKEN_MESSENGER_ADDRESS=0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5 27 | 28 | 29 | ################################# Ownership ################################## 30 | 31 | 32 | ### Owner Assistant (evm address) 33 | export RELEASE_OWNER_ASSISTANT_ADDRESS=0x 34 | 35 | 36 | ############################### Token Router ################################# 37 | 38 | 39 | ### Token Router Proxy (evm address) 40 | export RELEASE_TOKEN_ROUTER_ADDRESS=0xa098368AaaDc0FdF3e309cda710D7A5f8BDEeCD9 41 | 42 | 43 | ############################### Matching Engine ############################### 44 | 45 | 46 | ### Matching Engine Proxy (universal evm address) 47 | export RELEASE_MATCHING_ENGINE_CHAIN=1 48 | export RELEASE_MATCHING_ENGINE_ADDRESS=0x3e374fcd3aaf2ed067f3c93d21416855ec7916cfd2c2127bcbc68b3b1fb73077 49 | export RELEASE_MATCHING_ENGINE_MINT_RECIPIENT=0x58b82fca98f022ca29cfe822d1a3f92930020970910f0a99a0d9b995b21f99d5 50 | export RELEASE_MATCHING_ENGINE_DOMAIN=5 51 | 52 | 53 | ############################## Test Params ############################### 54 | 55 | 56 | export TEST_AMOUNT_IN=500000000 57 | export TEST_TARGET_CHAIN= 58 | export TEST_REDEEMER= 59 | export TEST_IS_FAST=true 60 | export TEST_FEE= 61 | export TEST_DEADLINE=0 62 | -------------------------------------------------------------------------------- /evm/env/testnet/Sepolia.env: -------------------------------------------------------------------------------- 1 | export RELEASE_CHAIN_TYPE=evm 2 | 3 | ### RPC 4 | export RPC= 5 | 6 | ### Wormhole Chain ID (uint16) 7 | export RELEASE_CHAIN_ID=10002 8 | 9 | ### Circle Domain (uint32) 10 | export RELEASE_DOMAIN=0 11 | 12 | ### Token (evm address) 13 | ### 14 | ### Token Info: https://sepolia.etherscan.io/address/0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 15 | export RELEASE_TOKEN_ADDRESS=0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 16 | 17 | 18 | ############################# Wormhole Contracts ############################# 19 | 20 | ### Wormhole (evm address) 21 | export RELEASE_WORMHOLE_ADDRESS=0x4a8bc80Ed5a4067f1CCf107057b8270E0cC11A78 22 | 23 | 24 | ############################# Circle Contracts ################################# 25 | 26 | ### Circle Token Messenger (evm address) 27 | export RELEASE_TOKEN_MESSENGER_ADDRESS=0x9f3B8679c73C2Fef8b59B4f3444d4e156fb70AA5 28 | 29 | 30 | ################################# Ownership ################################## 31 | 32 | 33 | ### Owner Assistant (evm address) 34 | export RELEASE_OWNER_ASSISTANT_ADDRESS=0x 35 | 36 | 37 | ############################### Token Router ################################# 38 | 39 | 40 | ### Token Router Proxy (evm address) 41 | export RELEASE_TOKEN_ROUTER_ADDRESS=0xE57D917bf955FedE2888AAbD056202a6497F1882 42 | 43 | 44 | ############################### Matching Engine ############################### 45 | 46 | 47 | ### Matching Engine Proxy (universal evm address) 48 | export RELEASE_MATCHING_ENGINE_CHAIN=1 49 | export RELEASE_MATCHING_ENGINE_ADDRESS=0x3e374fcd3aaf2ed067f3c93d21416855ec7916cfd2c2127bcbc68b3b1fb73077 50 | export RELEASE_MATCHING_ENGINE_MINT_RECIPIENT=0x58b82fca98f022ca29cfe822d1a3f92930020970910f0a99a0d9b995b21f99d5 51 | export RELEASE_MATCHING_ENGINE_DOMAIN=5 52 | 53 | 54 | ############################## Test Params ############################### 55 | 56 | 57 | export TEST_AMOUNT_IN=500000000 58 | export TEST_TARGET_CHAIN= 59 | export TEST_REDEEMER= 60 | export TEST_IS_FAST=true 61 | export TEST_FEE= 62 | export TEST_DEADLINE=0 63 | -------------------------------------------------------------------------------- /evm/forge/modules/circle/IUSDC.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.19; 3 | 4 | interface IUSDC { 5 | function mint(address to, uint256 amount) external; 6 | 7 | function configureMinter(address minter, uint256 minterAllowedAmount) external; 8 | 9 | function masterMinter() external view returns (address); 10 | 11 | function owner() external view returns (address); 12 | 13 | function blacklister() external view returns (address); 14 | 15 | // IERC20 16 | function totalSupply() external view returns (uint256); 17 | 18 | function balanceOf(address account) external view returns (uint256); 19 | 20 | function transfer(address to, uint256 amount) external returns (bool); 21 | 22 | function allowance(address owner, address spender) external view returns (uint256); 23 | 24 | function approve(address spender, uint256 amount) external returns (bool); 25 | 26 | function transferFrom(address from, address to, uint256 amount) external returns (bool); 27 | 28 | function decimals() external view returns (uint8); 29 | } 30 | -------------------------------------------------------------------------------- /evm/forge/modules/wormhole/IWETH.sol: -------------------------------------------------------------------------------- 1 | // contracts/Bridge.sol 2 | // SPDX-License-Identifier: Apache 2 3 | 4 | pragma solidity ^0.8.0; 5 | 6 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 7 | 8 | interface IWETH is IERC20 { 9 | function deposit() external payable; 10 | function withdraw(uint256 amount) external; 11 | } 12 | -------------------------------------------------------------------------------- /evm/forge/scripts/TestTransfer.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Script.sol"; 6 | import "forge-std/console2.sol"; 7 | 8 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 9 | import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; 10 | 11 | import "src/interfaces/ITokenRouter.sol"; 12 | 13 | contract TestTransfer is Script { 14 | uint16 immutable _chainId = uint16(vm.envUint("RELEASE_CHAIN_ID")); 15 | address immutable _token = vm.envAddress("RELEASE_TOKEN_ADDRESS"); 16 | address immutable _router = vm.envAddress("RELEASE_TOKEN_ROUTER_ADDRESS"); 17 | 18 | // Transfer params. 19 | uint64 _amountIn = uint64(vm.envUint("TEST_AMOUNT_IN")); 20 | uint16 _targetChain = uint16(vm.envUint("TEST_TARGET_CHAIN")); 21 | bytes32 _redeemer = vm.envBytes32("TEST_REDEEMER"); 22 | bool isFast = vm.envBool("TEST_IS_FAST"); 23 | bytes _redeemerMessage = hex"deadbeef"; 24 | uint64 _maxFee = uint64(vm.envUint("TEST_FEE")); 25 | uint32 _deadline = uint32(vm.envUint("TEST_DEADLINE")); 26 | 27 | function transfer() public { 28 | SafeERC20.safeIncreaseAllowance(IERC20(_token), _router, _amountIn); 29 | if (isFast) { 30 | ITokenRouter(_router).placeFastMarketOrder( 31 | _amountIn, _targetChain, _redeemer, _redeemerMessage, _maxFee, _deadline 32 | ); 33 | } else { 34 | ITokenRouter(_router).placeMarketOrder( 35 | _amountIn, _targetChain, _redeemer, _redeemerMessage 36 | ); 37 | } 38 | } 39 | 40 | function run() public { 41 | // Begin sending transactions. 42 | vm.startBroadcast(); 43 | 44 | transfer(); 45 | 46 | // Done. 47 | vm.stopBroadcast(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /evm/forge/scripts/UpgradeTokenRouter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Script.sol"; 6 | import "forge-std/console2.sol"; 7 | 8 | import {TokenRouter} from "src/TokenRouter/TokenRouter.sol"; 9 | import {ITokenRouter} from "src/interfaces/ITokenRouter.sol"; 10 | 11 | import {CheckWormholeContracts} from "./helpers/CheckWormholeContracts.sol"; 12 | 13 | import {Utils} from "src/shared/Utils.sol"; 14 | 15 | contract UpgradeTokenRouter is CheckWormholeContracts, Script { 16 | using Utils for address; 17 | 18 | uint16 immutable _chainId = uint16(vm.envUint("RELEASE_CHAIN_ID")); 19 | address immutable _tokenRouterAddress = vm.envAddress("RELEASE_TOKEN_ROUTER_ADDRESS"); 20 | 21 | address immutable _token = vm.envAddress("RELEASE_TOKEN_ADDRESS"); 22 | address immutable _wormhole = vm.envAddress("RELEASE_WORMHOLE_ADDRESS"); 23 | address immutable _cctpTokenMessenger = vm.envAddress("RELEASE_TOKEN_MESSENGER_ADDRESS"); 24 | bytes32 immutable _matchingEngineAddress = vm.envBytes32("RELEASE_MATCHING_ENGINE_ADDRESS"); 25 | bytes32 immutable _matchingEngineMintRecipient = 26 | vm.envBytes32("RELEASE_MATCHING_ENGINE_MINT_RECIPIENT"); 27 | uint16 immutable _matchingEngineChain = uint16(vm.envUint("RELEASE_MATCHING_ENGINE_CHAIN")); 28 | uint32 immutable _matchingEngineDomain = uint32(vm.envUint("RELEASE_MATCHING_ENGINE_DOMAIN")); 29 | 30 | function upgrade() public { 31 | requireValidChain(_chainId, _wormhole); 32 | 33 | TokenRouter implementation = new TokenRouter( 34 | _token, 35 | _wormhole, 36 | _cctpTokenMessenger, 37 | _matchingEngineChain, 38 | _matchingEngineAddress, 39 | _matchingEngineMintRecipient, 40 | _matchingEngineDomain 41 | ); 42 | 43 | ITokenRouter(_tokenRouterAddress).upgradeContract(address(implementation)); 44 | } 45 | 46 | function run() public { 47 | // Begin sending transactions. 48 | vm.startBroadcast(); 49 | 50 | // Perform upgrade. 51 | upgrade(); 52 | 53 | // Done. 54 | vm.stopBroadcast(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /evm/forge/scripts/helpers/CheckWormholeContracts.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "forge-std/Script.sol"; 6 | import "forge-std/console2.sol"; 7 | 8 | import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; 9 | import { IWormhole } from "wormhole-solidity-sdk/interfaces/IWormhole.sol"; 10 | 11 | contract CheckWormholeContracts { 12 | function requireValidChain(uint16 chain, address wormhole) internal view { 13 | require(IWormhole(wormhole).chainId() == chain, "invalid wormhole chain ID"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /evm/forge/tests/helpers/mock/MockMatchingEngineImplementation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import {MatchingEngine} from "src/MatchingEngine/MatchingEngine.sol"; 6 | import {IMatchingEngine} from "src/interfaces/IMatchingEngine.sol"; 7 | 8 | interface IMockMatchingEngine is IMatchingEngine { 9 | function isUpgraded() external pure returns (bool); 10 | 11 | function getImplementation() external view returns (address); 12 | } 13 | 14 | contract MockMatchingEngineImplementation is MatchingEngine { 15 | constructor( 16 | address _token, 17 | address _wormhole, 18 | address _cctpTokenMessenger, 19 | uint24 _userPenaltyRewardBps, 20 | uint24 _initialPenaltyBps, 21 | uint8 _auctionDuration, 22 | uint8 _auctionGracePeriod, 23 | uint8 _auctionPenaltyBlocks 24 | ) 25 | MatchingEngine( 26 | _token, 27 | _wormhole, 28 | _cctpTokenMessenger, 29 | _userPenaltyRewardBps, 30 | _initialPenaltyBps, 31 | _auctionDuration, 32 | _auctionGracePeriod, 33 | _auctionPenaltyBlocks 34 | ) 35 | {} 36 | 37 | function isUpgraded() external pure returns (bool) { 38 | return true; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /evm/forge/tests/helpers/mock/MockTokenRouterImplementation.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import {TokenRouter} from "src/TokenRouter/TokenRouter.sol"; 6 | import {ITokenRouter} from "src/interfaces/ITokenRouter.sol"; 7 | 8 | interface IMockTokenRouter is ITokenRouter { 9 | function isUpgraded() external pure returns (bool); 10 | 11 | function getImplementation() external view returns (address); 12 | } 13 | 14 | contract MockTokenRouterImplementation is TokenRouter { 15 | constructor( 16 | address _token, 17 | address _wormhole, 18 | address _cctpTokenMessenger, 19 | uint16 _matchingEngineChain, 20 | bytes32 _matchingEngineAddress, 21 | bytes32 _matchingEngineMintRecipient, 22 | uint32 _matchingEngineDomain 23 | ) 24 | TokenRouter( 25 | _token, 26 | _wormhole, 27 | _cctpTokenMessenger, 28 | _matchingEngineChain, 29 | _matchingEngineAddress, 30 | _matchingEngineMintRecipient, 31 | _matchingEngineDomain 32 | ) 33 | {} 34 | 35 | function isUpgraded() external pure returns (bool) { 36 | return true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /evm/foundry.toml: -------------------------------------------------------------------------------- 1 | [fmt] 2 | line_length = 100 3 | 4 | [profile.default] 5 | solc_version = "0.8.19" 6 | optimizer = true 7 | optimizer_runs = 200 8 | via_ir = true 9 | extra_output = [ 10 | "metadata", 11 | "storageLayout", 12 | "evm.deployedBytecode.immutableReferences", 13 | "evm.bytecode.opcodes" 14 | ] 15 | 16 | test = "forge/tests" 17 | 18 | libs = [ 19 | "lib", 20 | "node_modules", 21 | ] 22 | 23 | remappings = [ 24 | "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/", 25 | "forge-std/=lib/forge-std/src/", 26 | "src/=src/", 27 | "local-modules/=forge/modules/" 28 | ] 29 | 30 | gas_limit = "18446744073709551615" 31 | 32 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options -------------------------------------------------------------------------------- /evm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wormhole-foundation/example-liquidity-layer-evm", 3 | "version": "0.4.0", 4 | "author": "Wormhole Contributors", 5 | "license": "Apache-2.0", 6 | "main": "./dist/cjs/index.js", 7 | "types": "./dist/cjs/index.d.ts", 8 | "module": "./dist/esm/index.js", 9 | "files": [ 10 | "dist/cjs", 11 | "dist/esm" 12 | ], 13 | "scripts": { 14 | "build:esm": "tsc -p tsconfig.esm.json", 15 | "build:cjs": "tsc -p tsconfig.cjs.json", 16 | "build": "npm run build:esm && npm run build:cjs", 17 | "generate": "typechain --target=ethers-v5 --out-dir=ts/src/types out/[!build-info]*/*.json", 18 | "clean": "rm -rf dist && rm -rf node_modules && rm -f ./*.tsbuildinfo" 19 | }, 20 | "exports": { 21 | ".": { 22 | "import": "./dist/esm/index.js", 23 | "require": "./dist/cjs/index.js", 24 | "types": "./dist/cjs/index.d.ts" 25 | }, 26 | "./*": { 27 | "import": "./dist/esm/*/index.js", 28 | "require": "./dist/cjs/*/index.js", 29 | "types": "./dist/cjs/*/index.d.ts" 30 | } 31 | }, 32 | "dependencies": { 33 | "@wormhole-foundation/example-liquidity-layer-definitions": "0.4.0", 34 | "@wormhole-foundation/sdk-base": "^1.4.4", 35 | "@wormhole-foundation/sdk-definitions": "^1.4.4", 36 | "@wormhole-foundation/sdk-evm": "^1.4.4", 37 | "ethers-v5": "npm:ethers@^5.7.2" 38 | }, 39 | "devDependencies": { 40 | "@typechain/ethers-v5": "^10.2.0", 41 | "@types/chai": "^4.3.4", 42 | "@types/mocha": "^10.0.1", 43 | "@types/node": "^18.14.5", 44 | "chai": "^4.3.7", 45 | "dotenv": "^16.3.1", 46 | "envfile": "^7.1.0", 47 | "mocha": "^10.0.0", 48 | "prettier": "^2.8.7", 49 | "prettier-plugin-solidity": "^1.1.3", 50 | "ts-mocha": "^10.0.0", 51 | "typechain": "^8.1.1" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /evm/sh/configure_matching_engine.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":n:c:u:k:" opt; do 4 | case $opt in 5 | n) network="$OPTARG" 6 | ;; 7 | c) chain="$OPTARG" 8 | ;; 9 | k) private_key="$OPTARG" 10 | ;; 11 | \?) echo "Invalid option -$OPTARG" >&2 12 | exit 1 13 | ;; 14 | esac 15 | 16 | case $OPTARG in 17 | -*) echo "Option $opt needs a valid argument" >&2 18 | exit 1 19 | ;; 20 | esac 21 | done 22 | 23 | if [ -z ${network+x} ]; 24 | then 25 | echo "network (-n) is unset" >&2 26 | exit 1 27 | fi 28 | 29 | if [ -z ${chain+x} ]; 30 | then 31 | echo "chain (-c) is unset" >&2 32 | exit 1 33 | fi 34 | 35 | if [ -z ${private_key+x} ]; 36 | then 37 | echo "private key (-k) is unset" >&2 38 | exit 1 39 | fi 40 | 41 | set -euo pipefail 42 | 43 | ROOT=$(dirname $0) 44 | ENV=$ROOT/../env 45 | TARGET=$ROOT/../ts/scripts/setup_matching_engine.ts 46 | 47 | . $ENV/$network/$chain.env 48 | 49 | npx ts-node $TARGET --network $network --chain $chain --rpc $RPC --key $private_key 50 | 51 | -------------------------------------------------------------------------------- /evm/sh/configure_token_router.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":n:c:u:k:" opt; do 4 | case $opt in 5 | n) network="$OPTARG" 6 | ;; 7 | c) chain="$OPTARG" 8 | ;; 9 | k) private_key="$OPTARG" 10 | ;; 11 | \?) echo "Invalid option -$OPTARG" >&2 12 | exit 1 13 | ;; 14 | esac 15 | 16 | case $OPTARG in 17 | -*) echo "Option $opt needs a valid argument" >&2 18 | exit 1 19 | ;; 20 | esac 21 | done 22 | 23 | if [ -z ${network+x} ]; 24 | then 25 | echo "network (-n) is unset" >&2 26 | exit 1 27 | fi 28 | 29 | if [ -z ${chain+x} ]; 30 | then 31 | echo "chain (-c) is unset" >&2 32 | exit 1 33 | fi 34 | 35 | if [ -z ${private_key+x} ]; 36 | then 37 | echo "private key (-k) is unset" >&2 38 | exit 1 39 | fi 40 | 41 | set -euo pipefail 42 | 43 | ROOT=$(dirname $0) 44 | ENV=$ROOT/../env 45 | TARGET=$ROOT/../ts/scripts/setup_token_router.ts 46 | 47 | . $ENV/$network/$chain.env 48 | 49 | npx ts-node $TARGET --network $network --chain $chain --rpc $RPC --key $private_key 50 | 51 | -------------------------------------------------------------------------------- /evm/sh/deploy_matching_engine.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":n:c:u:k:" opt; do 4 | case $opt in 5 | n) network="$OPTARG" 6 | ;; 7 | c) chain="$OPTARG" 8 | ;; 9 | u) rpc="$OPTARG" 10 | ;; 11 | k) private_key="$OPTARG" 12 | ;; 13 | \?) echo "Invalid option -$OPTARG" >&2 14 | exit 1 15 | ;; 16 | esac 17 | 18 | case $OPTARG in 19 | -*) echo "Option $opt needs a valid argument" >&2 20 | exit 1 21 | ;; 22 | esac 23 | done 24 | 25 | if [ -z ${network+x} ]; 26 | then 27 | echo "network (-n) is unset" >&2 28 | exit 1 29 | fi 30 | 31 | if [ -z ${chain+x} ]; 32 | then 33 | echo "chain (-c) is unset" >&2 34 | exit 1 35 | fi 36 | 37 | if [ -z ${private_key+x} ]; 38 | then 39 | echo "private key (-k) is unset" >&2 40 | exit 1 41 | fi 42 | 43 | set -euo pipefail 44 | 45 | ROOT=$(dirname $0) 46 | ENV=$ROOT/../env 47 | FORGE_SCRIPTS=$ROOT/../forge/scripts 48 | 49 | . $ENV/$network/$chain.env 50 | 51 | # Use the RPC environment variable if rpc isn't set. 52 | if [ -z ${rpc+x} ]; 53 | then 54 | rpc=$RPC 55 | fi 56 | 57 | forge script $FORGE_SCRIPTS/DeployMatchingEngineContract.s.sol \ 58 | --rpc-url $rpc \ 59 | --broadcast \ 60 | --private-key $private_key 61 | -------------------------------------------------------------------------------- /evm/sh/deploy_token_router.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":n:c:u:k:" opt; do 4 | case $opt in 5 | n) network="$OPTARG" 6 | ;; 7 | c) chain="$OPTARG" 8 | ;; 9 | u) rpc="$OPTARG" 10 | ;; 11 | k) private_key="$OPTARG" 12 | ;; 13 | \?) echo "Invalid option -$OPTARG" >&2 14 | exit 1 15 | ;; 16 | esac 17 | 18 | case $OPTARG in 19 | -*) echo "Option $opt needs a valid argument" >&2 20 | exit 1 21 | ;; 22 | esac 23 | done 24 | 25 | if [ -z ${network+x} ]; 26 | then 27 | echo "network (-n) is unset" >&2 28 | exit 1 29 | fi 30 | 31 | if [ -z ${chain+x} ]; 32 | then 33 | echo "chain (-c) is unset" >&2 34 | exit 1 35 | fi 36 | 37 | if [ -z ${private_key+x} ]; 38 | then 39 | echo "private key (-k) is unset" >&2 40 | exit 1 41 | fi 42 | 43 | set -euo pipefail 44 | 45 | ROOT=$(dirname $0) 46 | ENV=$ROOT/../env 47 | FORGE_SCRIPTS=$ROOT/../forge/scripts 48 | 49 | . $ENV/$network/$chain.env 50 | 51 | # Use the RPC environment variable if rpc isn't set. 52 | if [ -z ${rpc+x} ]; 53 | then 54 | rpc=$RPC 55 | fi 56 | 57 | forge script $FORGE_SCRIPTS/DeployTokenRouterContracts.s.sol \ 58 | --rpc-url $rpc \ 59 | --broadcast \ 60 | --private-key $private_key 61 | -------------------------------------------------------------------------------- /evm/sh/test_transfer.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":n:c:u:k:" opt; do 4 | case $opt in 5 | n) network="$OPTARG" 6 | ;; 7 | c) chain="$OPTARG" 8 | ;; 9 | k) private_key="$OPTARG" 10 | ;; 11 | \?) echo "Invalid option -$OPTARG" >&2 12 | exit 1 13 | ;; 14 | esac 15 | 16 | case $OPTARG in 17 | -*) echo "Option $opt needs a valid argument" >&2 18 | exit 1 19 | ;; 20 | esac 21 | done 22 | 23 | if [ -z ${network+x} ]; 24 | then 25 | echo "network (-n) is unset" >&2 26 | exit 1 27 | fi 28 | 29 | if [ -z ${chain+x} ]; 30 | then 31 | echo "chain (-c) is unset" >&2 32 | exit 1 33 | fi 34 | 35 | if [ -z ${private_key+x} ]; 36 | then 37 | echo "private key (-k) is unset" >&2 38 | exit 1 39 | fi 40 | 41 | set -euo pipefail 42 | 43 | ROOT=$(dirname $0) 44 | ENV=$ROOT/../env 45 | FORGE_SCRIPTS=$ROOT/../forge/scripts 46 | 47 | . $ENV/$network/$chain.env 48 | 49 | forge script $FORGE_SCRIPTS/TestTransfer.s.sol \ 50 | --rpc-url $RPC \ 51 | --broadcast \ 52 | --private-key $private_key -------------------------------------------------------------------------------- /evm/sh/update_auction_config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":n:c:u:k:" opt; do 4 | case $opt in 5 | n) network="$OPTARG" 6 | ;; 7 | c) chain="$OPTARG" 8 | ;; 9 | k) private_key="$OPTARG" 10 | ;; 11 | \?) echo "Invalid option -$OPTARG" >&2 12 | exit 1 13 | ;; 14 | esac 15 | 16 | case $OPTARG in 17 | -*) echo "Option $opt needs a valid argument" >&2 18 | exit 1 19 | ;; 20 | esac 21 | done 22 | 23 | if [ -z ${network+x} ]; 24 | then 25 | echo "network (-n) is unset" >&2 26 | exit 1 27 | fi 28 | 29 | if [ -z ${chain+x} ]; 30 | then 31 | echo "chain (-c) is unset" >&2 32 | exit 1 33 | fi 34 | 35 | if [ -z ${private_key+x} ]; 36 | then 37 | echo "private key (-k) is unset" >&2 38 | exit 1 39 | fi 40 | 41 | set -euo pipefail 42 | 43 | ROOT=$(dirname $0) 44 | ENV=$ROOT/../env 45 | TARGET=$ROOT/../ts/scripts/set_auction_config.ts 46 | 47 | . $ENV/$network/$chain.env 48 | 49 | npx ts-node $TARGET --network $network --chain $chain --rpc $RPC --key $private_key 50 | 51 | -------------------------------------------------------------------------------- /evm/sh/update_fast_transfer_parameters.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":n:c:u:k:" opt; do 4 | case $opt in 5 | n) network="$OPTARG" 6 | ;; 7 | c) chain="$OPTARG" 8 | ;; 9 | k) private_key="$OPTARG" 10 | ;; 11 | \?) echo "Invalid option -$OPTARG" >&2 12 | exit 1 13 | ;; 14 | esac 15 | 16 | case $OPTARG in 17 | -*) echo "Option $opt needs a valid argument" >&2 18 | exit 1 19 | ;; 20 | esac 21 | done 22 | 23 | if [ -z ${network+x} ]; 24 | then 25 | echo "network (-n) is unset" >&2 26 | exit 1 27 | fi 28 | 29 | if [ -z ${chain+x} ]; 30 | then 31 | echo "chain (-c) is unset" >&2 32 | exit 1 33 | fi 34 | 35 | if [ -z ${private_key+x} ]; 36 | then 37 | echo "private key (-k) is unset" >&2 38 | exit 1 39 | fi 40 | 41 | set -euo pipefail 42 | 43 | ROOT=$(dirname $0) 44 | ENV=$ROOT/../env 45 | TARGET=$ROOT/../ts/scripts/set_fast_transfer_parameters.ts 46 | 47 | . $ENV/$network/$chain.env 48 | 49 | npx ts-node $TARGET --network $network --chain $chain --rpc $RPC --key $private_key 50 | 51 | -------------------------------------------------------------------------------- /evm/sh/upgrade_matching_engine.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":n:c:u:k:" opt; do 4 | case $opt in 5 | n) network="$OPTARG" 6 | ;; 7 | c) chain="$OPTARG" 8 | ;; 9 | u) rpc="$OPTARG" 10 | ;; 11 | k) private_key="$OPTARG" 12 | ;; 13 | \?) echo "Invalid option -$OPTARG" >&2 14 | exit 1 15 | ;; 16 | esac 17 | 18 | case $OPTARG in 19 | -*) echo "Option $opt needs a valid argument" >&2 20 | exit 1 21 | ;; 22 | esac 23 | done 24 | 25 | if [ -z ${network+x} ]; 26 | then 27 | echo "network (-n) is unset" >&2 28 | exit 1 29 | fi 30 | 31 | if [ -z ${chain+x} ]; 32 | then 33 | echo "chain (-c) is unset" >&2 34 | exit 1 35 | fi 36 | 37 | if [ -z ${private_key+x} ]; 38 | then 39 | echo "private key (-k) is unset" >&2 40 | exit 1 41 | fi 42 | 43 | set -euo pipefail 44 | 45 | ROOT=$(dirname $0) 46 | ENV=$ROOT/../env 47 | FORGE_SCRIPTS=$ROOT/../forge/scripts 48 | 49 | . $ENV/$network/$chain.env 50 | 51 | # Use the RPC environment variable if rpc isn't set. 52 | if [ -z ${rpc+x} ]; 53 | then 54 | rpc=$RPC 55 | fi 56 | 57 | forge script $FORGE_SCRIPTS/UpgradeMatchingEngine.s.sol \ 58 | --rpc-url $rpc \ 59 | --broadcast \ 60 | --private-key $private_key 61 | -------------------------------------------------------------------------------- /evm/sh/upgrade_token_router.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | while getopts ":n:c:u:k:" opt; do 4 | case $opt in 5 | n) network="$OPTARG" 6 | ;; 7 | c) chain="$OPTARG" 8 | ;; 9 | u) rpc="$OPTARG" 10 | ;; 11 | k) private_key="$OPTARG" 12 | ;; 13 | \?) echo "Invalid option -$OPTARG" >&2 14 | exit 1 15 | ;; 16 | esac 17 | 18 | case $OPTARG in 19 | -*) echo "Option $opt needs a valid argument" >&2 20 | exit 1 21 | ;; 22 | esac 23 | done 24 | 25 | if [ -z ${network+x} ]; 26 | then 27 | echo "network (-n) is unset" >&2 28 | exit 1 29 | fi 30 | 31 | if [ -z ${chain+x} ]; 32 | then 33 | echo "chain (-c) is unset" >&2 34 | exit 1 35 | fi 36 | 37 | if [ -z ${private_key+x} ]; 38 | then 39 | echo "private key (-k) is unset" >&2 40 | exit 1 41 | fi 42 | 43 | set -euo pipefail 44 | 45 | ROOT=$(dirname $0) 46 | ENV=$ROOT/../env 47 | FORGE_SCRIPTS=$ROOT/../forge/scripts 48 | 49 | . $ENV/$network/$chain.env 50 | 51 | # Use the RPC environment variable if rpc isn't set. 52 | if [ -z ${rpc+x} ]; 53 | then 54 | rpc=$RPC 55 | fi 56 | 57 | forge script $FORGE_SCRIPTS/UpgradeTokenRouter.s.sol -vvvv \ 58 | --rpc-url $rpc \ 59 | --broadcast \ 60 | --private-key $private_key 61 | -------------------------------------------------------------------------------- /evm/src/MatchingEngine/assets/Errors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | error ErrInvalidWormholeMessage(string reason); 6 | 7 | error ErrChainNotAllowed(uint16 chain); 8 | 9 | error ErrInvalidEndpoint(bytes32 endpoint); 10 | 11 | error ErrEndpointAlreadyExists(uint16 chain); 12 | 13 | error ErrInvalidTargetRouter(uint16 chain); 14 | 15 | error ErrInvalidSourceRouter(bytes32 sender, bytes32 expected); 16 | 17 | error ErrBidPriceTooHigh(uint64 bidPrice, uint64 maxPrice); 18 | 19 | error ErrAuctionPeriodExpired(); 20 | 21 | error ErrAuctionAlreadyStarted(); 22 | 23 | error ErrAuctionNotActive(bytes32 auctionId); 24 | 25 | error ErrAuctionPeriodNotComplete(); 26 | 27 | error ErrVaaMismatch(); 28 | 29 | error ErrInvalidAuctionStatus(); 30 | 31 | error ErrInvalidEmitterForFastFill(); 32 | 33 | error ErrFastFillAlreadyRedeemed(); 34 | 35 | error ErrInvalidAuctionDuration(); 36 | 37 | error ErrInvalidAuctionGracePeriod(); 38 | 39 | error ErrInvalidUserPenaltyRewardBps(); 40 | 41 | error ErrInvalidInitialPenaltyBps(); 42 | 43 | error ErrDeadlineExceeded(); 44 | 45 | error ErrCallerNotDeployer(address deployer, address caller); 46 | 47 | error InvalidInitDataLength(uint256 actual, uint256 expected); 48 | -------------------------------------------------------------------------------- /evm/src/MatchingEngine/assets/Storage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "src/interfaces/IMatchingEngineTypes.sol"; 6 | 7 | // keccak256("FeeRecipient") - 1 8 | bytes32 constant FEE_RECIPIENT_STORAGE_SLOT = 9 | 0x8743f91cd3aa128615946fad71e2f5cfe95e4c35cccb2afbc7ecaa9a9b7f137f; 10 | 11 | function getFeeRecipientState() pure returns (FeeRecipient storage state) { 12 | assembly ("memory-safe") { 13 | state.slot := FEE_RECIPIENT_STORAGE_SLOT 14 | } 15 | } 16 | 17 | // keccak256("RouterEndpoints") - 1 18 | bytes32 constant ROUTER_ENDPOINT_STORAGE_SLOT = 19 | 0x3627fcf6b5d29b232a423d0b586326756a413529bc2286eb687a1a7d4123d9ff; 20 | 21 | function getRouterEndpointState() pure returns (RouterEndpoints storage state) { 22 | assembly ("memory-safe") { 23 | state.slot := ROUTER_ENDPOINT_STORAGE_SLOT 24 | } 25 | } 26 | 27 | // keccak256("LiveAuctionInfo") - 1 28 | bytes32 constant LIVE_AUCTION_INFO_STORAGE_SLOT = 29 | 0x18c32f0e31dd215bbecc21bc81c00cdff3cf52fdbe43432c8c0922334994dee1; 30 | 31 | function getLiveAuctionInfo() pure returns (LiveAuctionInfo storage state) { 32 | assembly ("memory-safe") { 33 | state.slot := LIVE_AUCTION_INFO_STORAGE_SLOT 34 | } 35 | } 36 | 37 | // keccak256("FastFills") - 1 38 | bytes32 constant TRANSFER_RECEIPTS_STORAGE_SLOT = 39 | 0xe58c46ab8c228ca315cb45e78f52803122060218943a20abb9ffec52c71706cc; 40 | 41 | function getFastFillsState() pure returns (FastFills storage state) { 42 | assembly ("memory-safe") { 43 | state.slot := TRANSFER_RECEIPTS_STORAGE_SLOT 44 | } 45 | } 46 | 47 | // keccak256("CircleDomain") - 1 48 | bytes32 constant CIRCLE_DOMAIN_STORAGE_SLOT = 49 | 0x0776d828ae37dc9b71ac8e092e28df60d7af2771b93454a1311c33040591339b; 50 | 51 | /** 52 | * @notice Returns the CircleDomains mapping. 53 | */ 54 | function getCircleDomainsState() pure returns (CircleDomains storage state) { 55 | assembly ("memory-safe") { 56 | state.slot := CIRCLE_DOMAIN_STORAGE_SLOT 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /evm/src/TokenRouter/assets/Errors.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | error ErrInvalidRedeemer(bytes32 redeemer, bytes32 expected); 6 | 7 | error ErrInvalidRefundAddress(); 8 | 9 | error ErrInvalidRedeemerAddress(); 10 | 11 | error ErrInvalidEndpoint(bytes32 endpoint); 12 | 13 | error ErrEndpointAlreadyExists(uint16 chain); 14 | 15 | error ErrUnsupportedChain(uint16 chain); 16 | 17 | error ErrChainNotAllowed(uint16 chain); 18 | 19 | error ErrInvalidSourceRouter(bytes32 sender, bytes32 expected); 20 | 21 | error ErrInvalidMatchingEngineSender(bytes32 sender, bytes32 expected); 22 | 23 | error ErrInvalidChain(uint16 chain); 24 | 25 | error ErrInsufficientAmount(uint64 amount, uint64 minAmount); 26 | 27 | error ErrInsufficientFastTransferFee(); 28 | 29 | error ErrAmountTooLarge(uint64 amountIn, uint64 maxAmount); 30 | 31 | error ErrFastTransferFeeUnset(); 32 | 33 | error ErrInvalidFeeInBps(); 34 | 35 | error ErrInvalidFastTransferParameters(); 36 | 37 | error ErrFastTransferNotSupported(); 38 | 39 | error ErrInvalidFeeOverride(); 40 | 41 | error ErrFastTransferDisabled(); 42 | 43 | error ErrInvalidMaxFee(uint64 maxFee, uint64 minimumReuiredFee); 44 | 45 | error ErrCallerNotDeployer(address deployer, address caller); 46 | 47 | error InvalidInitDataLength(uint256 actual, uint256 expected); 48 | 49 | error MaxPayloadSizeExceeded(uint256 actualSize, uint256 maxSize); 50 | -------------------------------------------------------------------------------- /evm/src/TokenRouter/assets/Storage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | import "src/interfaces/ITokenRouterTypes.sol"; 6 | 7 | // keccak256("RouterEndpoints") - 1 8 | bytes32 constant ROUTER_ENDPOINT_STORAGE_SLOT = 9 | 0x3627fcf6b5d29b232a423d0b586326756a413529bc2286eb687a1a7d4123d9ff; 10 | 11 | /** 12 | * @notice Returns the `RouterEndpoints` storage slot. 13 | */ 14 | function getRouterEndpointState() pure returns (RouterEndpoints storage state) { 15 | assembly ("memory-safe") { 16 | state.slot := ROUTER_ENDPOINT_STORAGE_SLOT 17 | } 18 | } 19 | 20 | // keccak256("FastTransferParameters") - 1 21 | bytes32 constant FAST_TRANSFER_PARAMETERS_STORAGE_SLOT = 22 | 0xb1fa150fa2d3e80815752aa4c585f31e33f15929e28258e784b10ef8d0560996; 23 | 24 | /** 25 | * @notice Returns the `FastTransferParameters` storage slot. 26 | */ 27 | function getFastTransferParametersState() pure returns (FastTransferParameters storage state) { 28 | assembly ("memory-safe") { 29 | state.slot := FAST_TRANSFER_PARAMETERS_STORAGE_SLOT 30 | } 31 | } 32 | 33 | // keccak256("CircleDomain") - 1 34 | bytes32 constant CIRCLE_DOMAIN_STORAGE_SLOT = 35 | 0x0776d828ae37dc9b71ac8e092e28df60d7af2771b93454a1311c33040591339b; 36 | 37 | /** 38 | * @notice Returns the CircleDomains mapping. 39 | */ 40 | function getCircleDomainsState() pure returns (CircleDomains storage state) { 41 | assembly ("memory-safe") { 42 | state.slot := CIRCLE_DOMAIN_STORAGE_SLOT 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /evm/src/interfaces/IMatchingEngine.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./IMatchingEngineState.sol"; 6 | import "./IMatchingEngineAdmin.sol"; 7 | import "./IMatchingEngineFastOrders.sol"; 8 | import "./IAdmin.sol"; 9 | import "src/shared/Messages.sol"; 10 | 11 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 12 | 13 | interface IMatchingEngine is 14 | IMatchingEngineFastOrders, 15 | IMatchingEngineState, 16 | IMatchingEngineAdmin, 17 | IAdmin 18 | {} 19 | -------------------------------------------------------------------------------- /evm/src/interfaces/IMatchingEngineAdmin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | import {RouterEndpoint} from "./IMatchingEngineTypes.sol"; 4 | 5 | pragma solidity ^0.8.0; 6 | 7 | interface IMatchingEngineAdmin { 8 | /** 9 | * @notice Add a `router` endpoint for the specified Wormhole `chain`. 10 | * @param chain The Wormhole chain ID. 11 | * @param endpoint The `Endpoint` for the specified `chain`. 12 | * @param circleDomain The Circle domain for the specified `chain`. 13 | * @dev This function is only callable by the contract owner or assistant. 14 | */ 15 | function addRouterEndpoint(uint16 chain, RouterEndpoint memory endpoint, uint32 circleDomain) 16 | external; 17 | 18 | /** 19 | * @notice Update a `router` endpoint for the specified Wormhole `chain`. 20 | * @param chain The Wormhole chain ID. 21 | * @param endpoint The `Endpoint` for the specified `chain`. 22 | * @param circleDomain The Circle domain for the specified `chain`. 23 | * @dev This function is only callable by the contract owner. 24 | */ 25 | function updateRouterEndpoint(uint16 chain, RouterEndpoint memory endpoint, uint32 circleDomain) 26 | external; 27 | 28 | /** 29 | * @notice Disable a `router` endpoint for the specified Wormhole `chain`. 30 | * @param chain The Wormhole chain ID. 31 | * @dev This function is only callable by the contract owner. 32 | */ 33 | function disableRouterEndpoint(uint16 chain) external; 34 | 35 | /** 36 | * @notice Updates the `feeRecipient` state variable. This method can 37 | * only be executed by the owner. 38 | * @param newFeeRecipient Address of the new `feeRecipient`. 39 | */ 40 | function updateFeeRecipient(address newFeeRecipient) external; 41 | 42 | /** 43 | * @notice Sets the allowance of the CCTP token for the token messenger. 44 | * @param amount The new allowance. 45 | */ 46 | function setCctpAllowance(uint256 amount) external; 47 | } 48 | -------------------------------------------------------------------------------- /evm/src/interfaces/IMatchingEngineTypes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | struct CctpMessage { 6 | // Signed wormhole message. 7 | bytes encodedWormholeMessage; 8 | // Message emitted by the CCTP contract when burning USDC. 9 | bytes circleBridgeMessage; 10 | // Attestation created by the CCTP off-chain process, which is needed to mint USDC. 11 | bytes circleAttestation; 12 | } 13 | 14 | struct FeeRecipient { 15 | // The address that receives the `baseFee` for relaying `SlowOrderResponse` messages. 16 | address recipient; 17 | } 18 | 19 | struct RouterEndpoint { 20 | bytes32 router; 21 | bytes32 mintRecipient; 22 | } 23 | 24 | struct RouterEndpoints { 25 | // Mapping of chain ID to router address in Wormhole universal format. 26 | mapping(uint16 chain => RouterEndpoint endpoint) endpoints; 27 | } 28 | 29 | enum AuctionStatus { 30 | None, 31 | Active, 32 | Completed 33 | } 34 | 35 | struct LiveAuctionData { 36 | // The auction status. 37 | AuctionStatus status; 38 | // The highest bidder of the auction. 39 | address highestBidder; 40 | // The initial bidder of the auction. 41 | address initialBidder; 42 | // The block number at which the auction started. 43 | uint64 startBlock; 44 | // The amount of tokens to be sent to the user. 45 | uint64 amount; 46 | // The additional deposit made by the highest bidder. 47 | uint64 securityDeposit; 48 | // The bid price of the highest bidder. 49 | uint64 bidPrice; 50 | } 51 | 52 | struct LiveAuctionInfo { 53 | // Mapping of Fast Order VAA hash to `LiveAuctionData`. 54 | mapping(bytes32 auctionId => LiveAuctionData data) auctions; 55 | } 56 | 57 | struct FastFills { 58 | // Mapping of VAA hash to redemption status. 59 | mapping(bytes32 vaaHash => bool redeemed) redeemed; 60 | } 61 | 62 | struct CircleDomains { 63 | // Mapping of chain ID to Circle domain. 64 | mapping(uint16 chain => uint32 domain) domains; 65 | } 66 | -------------------------------------------------------------------------------- /evm/src/interfaces/IRedeemFill.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import {OrderResponse} from "./ITokenRouterTypes.sol"; 6 | 7 | struct RedeemedFill { 8 | // The address of the `PlaceMarketOrder` caller on the source chain. 9 | bytes32 sender; 10 | // The chain ID of the source chain. 11 | uint16 senderChain; 12 | // The address of the USDC token that was transferred. 13 | address token; 14 | // The amount of USDC that was transferred. 15 | uint256 amount; 16 | // The arbitrary bytes message that was sent to the `redeemer` contract. 17 | bytes message; 18 | } 19 | 20 | interface IRedeemFill { 21 | /** 22 | * @notice Redeems a `Fill` or `FastFill` Wormhole message from a registered router 23 | * (or the `MatchingEngine` in the case of a `FastFill`). The `token` and `message` 24 | * are sent to the `redeemer` contract on the target chain. 25 | * @dev The caller must be the encoded `redeemer` in the `Fill` message. 26 | * @param response The `OrderResponse` struct containing the `Fill` message. 27 | * @return redeemedFill The `RedeemedFill` struct. 28 | */ 29 | function redeemFill(OrderResponse memory response) external returns (RedeemedFill memory); 30 | } 31 | -------------------------------------------------------------------------------- /evm/src/interfaces/ITokenRouter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | import "./ITokenRouterTypes.sol"; 6 | import "./IPlaceMarketOrder.sol"; 7 | import "./IRedeemFill.sol"; 8 | import "./ITokenRouterState.sol"; 9 | import "./ITokenRouterAdmin.sol"; 10 | import "./ITokenRouterEvents.sol"; 11 | import "./IAdmin.sol"; 12 | 13 | import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 14 | 15 | interface ITokenRouter is 16 | IPlaceMarketOrder, 17 | IRedeemFill, 18 | ITokenRouterState, 19 | ITokenRouterAdmin, 20 | ITokenRouterEvents, 21 | IAdmin 22 | {} 23 | -------------------------------------------------------------------------------- /evm/src/interfaces/ITokenRouterEvents.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | interface ITokenRouterEvents { 6 | /** 7 | * @notice Emitted when a fill is redeemed by this contract. 8 | * @param emitterChainId Wormhole chain ID of emitter on the source chain. 9 | * @param emitterAddress Address (bytes32 zero-left-padded) of emitter on the source chain. 10 | * @param sequence Sequence of the Wormhole message. 11 | */ 12 | event FillRedeemed( 13 | uint16 indexed emitterChainId, bytes32 indexed emitterAddress, uint64 indexed sequence 14 | ); 15 | } -------------------------------------------------------------------------------- /evm/src/interfaces/ITokenRouterTypes.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | struct OrderResponse { 6 | // Signed wormhole message. 7 | bytes encodedWormholeMessage; 8 | // Message emitted by the CCTP contract when burning USDC. 9 | bytes circleBridgeMessage; 10 | // Attestation created by the CCTP off-chain process, which is needed to mint USDC. 11 | bytes circleAttestation; 12 | } 13 | 14 | struct FastTransferParameters { 15 | // Determines if fast transfers are enabled. 16 | bool enabled; 17 | // The maximum amount that can be transferred using fast transfers. 18 | uint64 maxAmount; 19 | // The `baseFee` which is summed with the `feeInBps` to calculate the total fee. 20 | uint64 baseFee; 21 | // The fee paid to the initial bidder of an auction. 22 | uint64 initAuctionFee; 23 | } 24 | 25 | struct Endpoint { 26 | bytes32 router; 27 | bytes32 mintRecipient; 28 | } 29 | 30 | struct RouterEndpoints { 31 | // Mapping of chain ID to router address in Wormhole universal format. 32 | mapping(uint16 chain => Endpoint endpoint) endpoints; 33 | } 34 | 35 | struct CircleDomains { 36 | // Mapping of chain ID to Circle domain. 37 | mapping(uint16 chain => uint32 domain) domains; 38 | } 39 | -------------------------------------------------------------------------------- /evm/src/interfaces/external/ITokenMinter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | pragma solidity ^0.8.19; 3 | 4 | /** 5 | * @title ITokenMinter 6 | * @notice interface for minter of tokens that are mintable, burnable, and interchangeable 7 | * across domains. 8 | */ 9 | interface ITokenMinter { 10 | function burnLimitsPerMessage(address token) external view returns (uint256); 11 | 12 | function remoteTokensToLocalTokens(bytes32 sourceIdHash) external view returns (address); 13 | } 14 | -------------------------------------------------------------------------------- /evm/src/shared/Utils.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: Apache 2 2 | 3 | pragma solidity ^0.8.19; 4 | 5 | library Utils { 6 | error AddressOverflow(bytes32 addr); 7 | 8 | function toUniversalAddress(address evmAddr) internal pure returns (bytes32 converted) { 9 | assembly ("memory-safe") { 10 | converted := and(0xffffffffffffffffffffffffffffffffffffffff, evmAddr) 11 | } 12 | } 13 | 14 | function fromUniversalAddress(bytes32 universalAddr) 15 | internal 16 | pure 17 | returns (address converted) 18 | { 19 | if (bytes12(universalAddr) != 0) { 20 | revert AddressOverflow(universalAddr); 21 | } 22 | 23 | assembly ("memory-safe") { 24 | converted := universalAddr 25 | } 26 | } 27 | 28 | function revertBuiltIn(string memory reason) internal pure { 29 | // NOTE: Using require is the easy way to revert with the built-in Error type. 30 | require(false, reason); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /evm/ts/scripts/helpers/consts.ts: -------------------------------------------------------------------------------- 1 | export const MATCHING_ENGINE_ADDRESS = 2 | process.env.RELEASE_MATCHING_ENGINE_ENDPOINT!; 3 | export const ZERO_BYTES32 = 4 | "0x0000000000000000000000000000000000000000000000000000000000000000"; 5 | export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; 6 | -------------------------------------------------------------------------------- /evm/ts/scripts/helpers/helpers.ts: -------------------------------------------------------------------------------- 1 | import * as fs from "fs"; 2 | 3 | export function getConfig(network: string) { 4 | const config = JSON.parse( 5 | fs.readFileSync(`${__dirname}/../../../cfg/deployment.${network}.json`, "utf8") 6 | ); 7 | return config; 8 | } 9 | -------------------------------------------------------------------------------- /evm/ts/scripts/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./helpers"; 2 | export * from "./consts"; 3 | -------------------------------------------------------------------------------- /evm/ts/src/TokenRouter/index.ts: -------------------------------------------------------------------------------- 1 | import { LiquidityLayerTransactionResult, PreparedInstruction } from ".."; 2 | import { ethers } from "ethers-v5"; 3 | export * from "./evm"; 4 | 5 | export type FastTransferParameters = { 6 | enabled: boolean; 7 | maxAmount: bigint; 8 | baseFee: bigint; 9 | initAuctionFee: bigint; 10 | }; 11 | 12 | export type OrderResponse = { 13 | encodedWormholeMessage: Buffer | Uint8Array; 14 | circleBridgeMessage: Buffer | Uint8Array; 15 | circleAttestation: Buffer | Uint8Array; 16 | }; 17 | 18 | export type Endpoint = { 19 | router: string | Buffer | Uint8Array; 20 | mintRecipient: string | Buffer | Uint8Array; 21 | }; 22 | 23 | export abstract class TokenRouter { 24 | abstract get address(): string; 25 | 26 | abstract placeMarketOrder( 27 | amountIn: bigint, 28 | targetChain: number, 29 | redeemer: Buffer | Uint8Array, 30 | redeemerMessage: Buffer | Uint8Array, 31 | minAmountOut?: bigint, 32 | refundAddress?: string, 33 | ): Promise; 34 | 35 | abstract placeFastMarketOrder( 36 | amountIn: bigint, 37 | targetChain: number, 38 | redeemer: Buffer | Uint8Array, 39 | redeemerMessage: Buffer | Uint8Array, 40 | maxFee: bigint, 41 | deadline: number, 42 | minAmountOut?: bigint, 43 | refundAddress?: string, 44 | ): Promise; 45 | 46 | abstract redeemFill(response: OrderResponse): Promise; 47 | 48 | abstract addRouterEndpoint( 49 | chain: number, 50 | endpoint: Endpoint, 51 | domain: number, 52 | ): Promise; 53 | 54 | abstract updateFastTransferParameters( 55 | newParams: FastTransferParameters, 56 | ): Promise; 57 | 58 | abstract enableFastTransfer(enable: boolean): Promise; 59 | 60 | abstract getInitialAuctionFee(): Promise; 61 | 62 | abstract getTransactionResults(txHash: string): Promise; 63 | } 64 | -------------------------------------------------------------------------------- /evm/ts/src/error.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers-v5"; 2 | 3 | export type DecodedErr = { 4 | selector: string; 5 | data?: string; 6 | }; 7 | 8 | export function errorDecoder(ethersError: any): DecodedErr { 9 | if ( 10 | !("code" in ethersError) || 11 | !("error" in ethersError) || 12 | !("error" in ethersError.error) || 13 | !("error" in ethersError.error.error) || 14 | !("code" in ethersError.error.error.error) || 15 | !("data" in ethersError.error.error.error) 16 | ) { 17 | throw new Error("not contract error"); 18 | } 19 | 20 | const { data } = ethersError.error.error.error as { 21 | data: string; 22 | }; 23 | 24 | if (data.length < 10 || data.substring(0, 2) != "0x") { 25 | throw new Error("data not custom error"); 26 | } 27 | 28 | const selector = data.substring(0, 10); 29 | 30 | // TODO: implement all errors 31 | switch (selector) { 32 | case computeSelector("ErrDeadlineExceeded()"): { 33 | return { selector: "ErrDeadlineExceeded" }; 34 | } 35 | case computeSelector("ErrUnsupportedChain(uint16)"): { 36 | return { 37 | selector: "ErrUnsupportedChain", 38 | data: "0x" + data.substring(10), 39 | }; 40 | } 41 | case computeSelector("ErrInvalidSourceRouter(bytes32,bytes32)"): { 42 | return { 43 | selector: "ErrInvalidSourceRouter", 44 | data: "0x" + data.substring(10), 45 | }; 46 | } 47 | default: { 48 | throw new Error(`unknown selector: ${selector}`); 49 | } 50 | } 51 | } 52 | 53 | function computeSelector(methodSignature: string): string { 54 | return ethers.utils.keccak256(Buffer.from(methodSignature)).substring(0, 10); 55 | } 56 | -------------------------------------------------------------------------------- /evm/ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers-v5"; 2 | 3 | export * from "./MatchingEngine"; 4 | export * from "./TokenRouter"; 5 | export * from "./error"; 6 | export * from "./messages"; 7 | export * from "./utils"; 8 | 9 | export * as ethers_types from "./types"; 10 | 11 | export type PreparedInstruction = ethers.ContractTransaction; 12 | -------------------------------------------------------------------------------- /evm/ts/src/testing/consts.ts: -------------------------------------------------------------------------------- 1 | import { FastTransferParameters } from ".."; 2 | 3 | export type ValidNetwork = "Avalanche" | "Ethereum" | "Base"; 4 | 5 | export type NetworkVars = { 6 | [key in ValidNetwork]?: T; 7 | }; 8 | 9 | // Avalanche Mainnet Fork 10 | export const LOCALHOSTS: NetworkVars = { 11 | Avalanche: "http://127.0.0.1:8547", 12 | Ethereum: "http://127.0.0.1:8548", 13 | Base: "http://127.0.0.1:8549", 14 | }; 15 | 16 | export const USDC_DECIMALS: NetworkVars = { 17 | Avalanche: 6, 18 | Ethereum: 6, 19 | Base: 6, 20 | }; 21 | 22 | export const WORMHOLE_MESSAGE_FEE = 100000000000000n; 23 | export const WORMHOLE_GUARDIAN_SET_INDEX = 4; 24 | export const GUARDIAN_PRIVATE_KEY = 25 | "cfb12303a19cde580bb4dd771639b0d26bc68353645571a8cff516ab2ee113a0"; 26 | export const WALLET_PRIVATE_KEYS = [ 27 | "4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d", 28 | "0x6cbed15c793ce57650b9877cf6fa156fbef513c4e6134f022a85b1ffdd59b2a1", 29 | "0x6370fd033278c143179d81c5526140625662b8daa446c22ee2d73db3707e620c", 30 | "0x646f1ce2fdad0e6deeeb5c7e8e5543bdde65e86029e2fd9fc169899c440a7913", 31 | "0xadd53f9a7e588d003326d1cbf9e4a43c061aadd9bc938c843a79e7b4fd2ad743", 32 | "0x395df67f0c2d2d9fe1ad08d1bc8b6627011959b79c53d7dd6a3536a33ab8a4fd", 33 | "0xe485d098507f54e7733a205420dfddbe58db035fa577fc294ebd14db90767a52", 34 | "0xa453611d9419d0e56f499079478fd72c37b251a94bfde4d19872c44cf65386e3", 35 | "0x829e924fdf021ba3dbbc4225edfece9aca04b929d6e75613329ca6f1d31c0bb4", 36 | "0xb0057716d5917badaf911b193b12b910811c1497b5bada8d7711f758981c3773", 37 | ]; 38 | 39 | // Arbitrarily decided. 40 | export const OWNER_PRIVATE_KEY = WALLET_PRIVATE_KEYS[9]; 41 | export const OWNER_ASSISTANT_PRIVATE_KEY = WALLET_PRIVATE_KEYS[8]; 42 | 43 | export const MATCHING_ENGINE_CHAIN = 6; 44 | export const MATCHING_ENGINE_NAME = "Avalanche"; 45 | 46 | export const DEFAULT_FAST_TRANSFER_PARAMS: FastTransferParameters = { 47 | enabled: true, 48 | maxAmount: BigInt(500000000000), 49 | baseFee: BigInt(100000), 50 | initAuctionFee: BigInt(100000), 51 | }; 52 | -------------------------------------------------------------------------------- /evm/ts/src/testing/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./consts"; 2 | export * from "./mock"; 3 | export * from "./utils"; 4 | export * from "./env"; 5 | -------------------------------------------------------------------------------- /evm/ts/src/testing/mock/circleAttester.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers-v5"; 2 | import { GUARDIAN_PRIVATE_KEY } from "../consts"; 3 | 4 | export type Attestation = { 5 | circleBridgeMessage: Buffer; 6 | circleAttestation: Buffer; 7 | }; 8 | 9 | export class CircleAttester { 10 | attester: ethers.utils.SigningKey; 11 | 12 | constructor() { 13 | this.attester = new ethers.utils.SigningKey("0x" + GUARDIAN_PRIVATE_KEY); 14 | } 15 | 16 | createAttestation(message: Buffer | Uint8Array) { 17 | const signature = this.attester.signDigest(ethers.utils.keccak256(message)); 18 | 19 | const attestation = Buffer.alloc(65); 20 | attestation.set(ethers.utils.arrayify(signature.r), 0); 21 | attestation.set(ethers.utils.arrayify(signature.s), 32); 22 | 23 | const recoveryId = signature.recoveryParam; 24 | attestation.writeUInt8(recoveryId < 27 ? recoveryId + 27 : recoveryId, 64); 25 | 26 | return attestation; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /evm/ts/src/testing/mock/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@wormhole-foundation/sdk-base"; 2 | import { ethers } from "ethers-v5"; 3 | 4 | export * from "./circleAttester"; 5 | export * from "./wormhole"; 6 | 7 | export abstract class EvmObserver { 8 | abstract observeEvm( 9 | provider: ethers.providers.Provider, 10 | chain: Chain, 11 | txReceipt: ethers.ContractReceipt, 12 | ): Promise; 13 | 14 | abstract observeManyEvm( 15 | provider: ethers.providers.Provider, 16 | chain: Chain, 17 | txReceipt: ethers.ContractReceipt, 18 | ): Promise; 19 | } 20 | -------------------------------------------------------------------------------- /evm/ts/tests/run_integration_test.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | 3 | pgrep anvil > /dev/null 4 | if [ $? -eq 0 ]; then 5 | echo "anvil already running" 6 | exit 1; 7 | fi 8 | 9 | ROOT=$(dirname $0) 10 | 11 | . $ROOT/.env 12 | 13 | LOGS=$ROOT/.anvil 14 | mkdir -p $LOGS 15 | 16 | # Avalanche (ME and CCTP). 17 | anvil --port 8547 \ 18 | -m "myth like bonus scare over problem client lizard pioneer submit female collect" \ 19 | --no-mining \ 20 | --fork-url $AVALANCHE_RPC > $LOGS/avalanche.log & 21 | 22 | # Ethereum (CCTP). 23 | anvil --port 8548 \ 24 | -m "myth like bonus scare over problem client lizard pioneer submit female collect" \ 25 | --no-mining \ 26 | --fork-url $ETHEREUM_RPC > $LOGS/ethereum.log & 27 | 28 | # Chill. 29 | sleep 2 30 | 31 | # Double-check number of anvil instances. 32 | if [ "$( pgrep anvil | wc -l )" -ne 2 ]; then 33 | echo "Not all anvil instances are running. Try again." 34 | pkill anvil 35 | exit 1 36 | fi 37 | 38 | 39 | set -e 40 | npx ts-mocha -t 1000000 -p $ROOT/tsconfig.json --bail $ROOT/[0-9]*.ts 41 | 42 | # # Nuke. 43 | pkill anvil -------------------------------------------------------------------------------- /evm/ts/tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["node", "mocha", "chai"], 4 | "lib": ["es2020"], 5 | "module": "CommonJS", 6 | "target": "es2020", 7 | "esModuleInterop": true, 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "moduleResolution": "node" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /evm/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "outDir": "./dist/cjs" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /evm/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/esm" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /evm/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends":"../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./ts/src", 5 | }, 6 | "include": ["ts/src/**/*.ts"], 7 | "exclude": ["node_modules", "ts/scripts", "ts/tests"] 8 | } 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wormhole-foundation/example-liquidity-layer", 3 | "version": "0.4.0", 4 | "author": "Wormhole Contributors", 5 | "license": "Apache-2.0", 6 | "scripts": { 7 | "build": "npm run build --workspaces --if-present", 8 | "generate": "npm run generate --workspaces --if-present", 9 | "pack": "npm run build && npm pack --workspaces", 10 | "build:universal": "npm run build --workspace universal/ts", 11 | "clean": "npm run clean --workspaces --if-present" 12 | }, 13 | "devDependencies": { 14 | "typescript": "^5.4.5" 15 | }, 16 | "workspaces": [ 17 | "universal/ts", 18 | "evm", 19 | "solana", 20 | "solver" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /solana/.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .env 3 | .validator_pid 4 | **/*.rs.bk 5 | /artifacts-* 6 | /cfg/**/*.json 7 | !/cfg/**/sample.*.json 8 | /node_modules 9 | target 10 | /ts/tests/artifacts 11 | dist 12 | *.tsbuildinfo -------------------------------------------------------------------------------- /solana/clippy.toml: -------------------------------------------------------------------------------- 1 | ### We prefer lazy evaluation to save on compute units. 2 | disallowed-methods = [ 3 | { path = "std::option::Option::and", reason = "prefer `and_then` for lazy evaluation" }, 4 | { path = "std::option::Option::map_or", reason = "prefer `map_or_else` for lazy evaluation" }, 5 | { path = "std::option::Option::ok_or", reason = "prefer `ok_or_else` for lazy evaluation" }, 6 | { path = "std::option::Option::unwrap_or", reason = "prefer `unwrap_or_else` for lazy evaluation" }, 7 | ] -------------------------------------------------------------------------------- /solana/modules/common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "liquidity-layer-common-solana" 3 | edition.workspace = true 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [features] 13 | mainnet = ["wormhole-solana-consts/mainnet", "wormhole-cctp-solana/mainnet"] 14 | testnet = ["wormhole-solana-consts/testnet", "wormhole-cctp-solana/testnet"] 15 | localnet = ["wormhole-solana-consts/mainnet", "wormhole-cctp-solana/mainnet"] 16 | idl-build = ["localnet", "anchor-lang/idl-build"] 17 | 18 | [dependencies] 19 | liquidity-layer-messages.workspace = true 20 | 21 | wormhole-cctp-solana = { workspace = true, features = ["cpi"] } 22 | 23 | wormhole-solana-consts.workspace = true 24 | 25 | anchor-lang.workspace = true 26 | solana-program.workspace = true 27 | cfg-if.workspace = true 28 | 29 | [lints] 30 | workspace = true 31 | -------------------------------------------------------------------------------- /solana/modules/common/src/admin/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod utils; 2 | 3 | use anchor_lang::prelude::Pubkey; 4 | 5 | pub trait Ownable { 6 | fn owner(&self) -> &Pubkey; 7 | 8 | fn owner_mut(&mut self) -> &mut Pubkey; 9 | } 10 | 11 | pub trait PendingOwner: Ownable { 12 | fn pending_owner(&self) -> &Option; 13 | 14 | fn pending_owner_mut(&mut self) -> &mut Option; 15 | } 16 | 17 | pub trait OwnerAssistant: Ownable { 18 | fn owner_assistant(&self) -> &Pubkey; 19 | 20 | fn owner_assistant_mut(&mut self) -> &mut Pubkey; 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | // use super::*; 26 | // TODO 27 | } 28 | -------------------------------------------------------------------------------- /solana/modules/common/src/admin/utils/assistant.rs: -------------------------------------------------------------------------------- 1 | use crate::admin::OwnerAssistant; 2 | use anchor_lang::prelude::*; 3 | 4 | pub fn only_owner_assistant( 5 | acct: &Account, 6 | owner_assistant: &Signer, 7 | custom_error: Error, 8 | ) -> Result 9 | where 10 | A: OwnerAssistant + Clone + AccountSerialize + AccountDeserialize, 11 | { 12 | if acct.owner_assistant() == &owner_assistant.key() { 13 | Ok(true) 14 | } else { 15 | Err(custom_error.with_pubkeys((*acct.owner_assistant(), owner_assistant.key()))) 16 | } 17 | } 18 | 19 | pub fn only_authorized( 20 | acct: &Account, 21 | owner_or_assistant: &Signer, 22 | custom_error: Error, 23 | ) -> Result 24 | where 25 | A: OwnerAssistant + Clone + AccountSerialize + AccountDeserialize, 26 | { 27 | if acct.owner() == &owner_or_assistant.key() 28 | || acct.owner_assistant() == &owner_or_assistant.key() 29 | { 30 | Ok(true) 31 | } else { 32 | Err(custom_error) 33 | } 34 | } 35 | 36 | pub fn transfer_owner_assistant(acct: &mut Account, new_assistant: &AccountInfo) 37 | where 38 | A: OwnerAssistant + Clone + AccountSerialize + AccountDeserialize, 39 | { 40 | *acct.owner_assistant_mut() = new_assistant.key(); 41 | } 42 | -------------------------------------------------------------------------------- /solana/modules/common/src/admin/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod assistant; 2 | 3 | pub mod ownable; 4 | 5 | pub mod pending_owner; 6 | 7 | pub mod upgrade; 8 | -------------------------------------------------------------------------------- /solana/modules/common/src/admin/utils/ownable.rs: -------------------------------------------------------------------------------- 1 | use crate::admin::Ownable; 2 | use anchor_lang::prelude::*; 3 | 4 | pub fn only_owner(acct: &Account, owner: &Signer, custom_error: Error) -> Result 5 | where 6 | A: Ownable + Clone + AccountSerialize + AccountDeserialize, 7 | { 8 | if acct.owner() == &owner.key() { 9 | Ok(true) 10 | } else { 11 | Err(custom_error.with_pubkeys((*acct.owner(), owner.key()))) 12 | } 13 | } 14 | 15 | pub fn transfer_ownership(acct: &mut Account, new_owner: &AccountInfo) 16 | where 17 | A: Ownable + Clone + AccountSerialize + AccountDeserialize, 18 | { 19 | *acct.owner_mut() = new_owner.key(); 20 | } 21 | -------------------------------------------------------------------------------- /solana/modules/common/src/admin/utils/pending_owner.rs: -------------------------------------------------------------------------------- 1 | use crate::admin::PendingOwner; 2 | use anchor_lang::prelude::*; 3 | 4 | pub fn only_pending_owner_unchecked(acct: &Account, pending_owner: &Pubkey) -> bool 5 | where 6 | A: PendingOwner + Clone + AccountSerialize + AccountDeserialize, 7 | { 8 | acct.pending_owner().unwrap() == *pending_owner 9 | } 10 | 11 | pub fn only_pending_owner(acct: &Account, pending_owner: &Pubkey) -> bool 12 | where 13 | A: PendingOwner + Clone + AccountSerialize + AccountDeserialize, 14 | { 15 | let pending = acct.pending_owner(); 16 | pending.is_some() && only_pending_owner_unchecked(acct, pending_owner) 17 | } 18 | 19 | pub fn transfer_ownership(acct: &mut Account, new_owner: &Pubkey) 20 | where 21 | A: PendingOwner + Clone + AccountSerialize + AccountDeserialize, 22 | { 23 | acct.pending_owner_mut().replace(*new_owner); 24 | } 25 | 26 | pub fn accept_ownership_unchecked(acct: &mut Account) 27 | where 28 | A: PendingOwner + Clone + AccountSerialize + AccountDeserialize, 29 | { 30 | *acct.owner_mut() = *acct.pending_owner().as_ref().unwrap(); 31 | *acct.pending_owner_mut() = None; 32 | } 33 | 34 | pub fn accept_ownership(acct: &mut Account) -> bool 35 | where 36 | A: PendingOwner + Clone + AccountSerialize + AccountDeserialize, 37 | { 38 | if acct.pending_owner().is_some() { 39 | accept_ownership_unchecked(acct); 40 | true 41 | } else { 42 | false 43 | } 44 | } 45 | 46 | pub fn cancel_transfer_ownership(acct: &mut Account) 47 | where 48 | A: PendingOwner + Clone + AccountSerialize + AccountDeserialize, 49 | { 50 | *acct.pending_owner_mut() = None; 51 | } 52 | -------------------------------------------------------------------------------- /solana/modules/common/src/admin/utils/upgrade.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | use solana_program::{instruction::Instruction, sysvar::instructions::load_instruction_at_checked}; 3 | 4 | pub trait RequireValidInstructionsError { 5 | fn require_eq_this_program(actual_program_id: Pubkey) -> Result<()>; 6 | 7 | fn require_eq_upgrade_manager(actual_program_id: Pubkey) -> Result<()>; 8 | } 9 | 10 | pub fn require_valid_instructions(instructions_sysvar: &AccountInfo) -> Result<()> 11 | where 12 | E: RequireValidInstructionsError, 13 | { 14 | // Check instruction to make sure this is executed top level as the first instruction. 15 | { 16 | let Instruction { program_id, .. } = load_instruction_at_checked(0, instructions_sysvar)?; 17 | E::require_eq_this_program(program_id)?; 18 | } 19 | 20 | // Check that the next instruction is the Upgrade Manager's. 21 | { 22 | let Instruction { program_id, .. } = load_instruction_at_checked(1, instructions_sysvar)?; 23 | E::require_eq_upgrade_manager(program_id)?; 24 | } 25 | 26 | // Done. 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /solana/modules/common/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use wormhole_cctp_solana; 2 | 3 | pub mod admin; 4 | 5 | pub use liquidity_layer_messages as messages; 6 | pub use messages::wormhole_io; 7 | 8 | pub const WORMHOLE_MESSAGE_NONCE: u32 = 0; 9 | 10 | pub const CORE_MESSAGE_SEED_PREFIX: &[u8] = b"core-msg"; 11 | pub const CCTP_MESSAGE_SEED_PREFIX: &[u8] = b"cctp-msg"; 12 | 13 | pub const TRANSFER_AUTHORITY_SEED_PREFIX: &[u8] = b"transfer-authority"; 14 | 15 | pub use wormhole_solana_consts::USDC_MINT; 16 | 17 | use solana_program::{pubkey, pubkey::Pubkey}; 18 | 19 | cfg_if::cfg_if! { 20 | if #[cfg(feature = "mainnet")] { 21 | pub const UPGRADE_MANAGER_PROGRAM_ID: Pubkey = pubkey!("4jyJ7EEsYa72REdD8ZMBvHFTXZ4VYGQPUHaJTajsK8SN"); 22 | pub const UPGRADE_MANAGER_AUTHORITY: Pubkey = pubkey!("Ag7BnUJ6C3mFXTaJfL2v9eJM2QbQ7GNLsDyewdCCLY8r"); 23 | } else if #[cfg(feature = "testnet")] { 24 | pub const UPGRADE_MANAGER_PROGRAM_ID: Pubkey = pubkey!("ucdP9ktgrXgEUnn6roqD2SfdGMR2JSiWHUKv23oXwxt"); 25 | pub const UPGRADE_MANAGER_AUTHORITY: Pubkey = pubkey!("2sxpm9pvWmNWFzhgWtmxkMsdWk2uSNT9MoKvww53po1M"); 26 | } else if #[cfg(feature = "localnet")] { 27 | pub const UPGRADE_MANAGER_PROGRAM_ID: Pubkey = pubkey!("UpgradeManager11111111111111111111111111111"); 28 | pub const UPGRADE_MANAGER_AUTHORITY: Pubkey = pubkey!("9Nu3k9HKFChDcAC8SeCrCeHvsRcdZzZfdQxGaEynFHZ7"); 29 | } 30 | } 31 | 32 | #[cfg(test)] 33 | mod test { 34 | use super::*; 35 | 36 | #[test] 37 | fn upgrade_manager_authority() { 38 | let (expected, _) = 39 | Pubkey::find_program_address(&[b"upgrade"], &UPGRADE_MANAGER_PROGRAM_ID); 40 | assert_eq!(UPGRADE_MANAGER_AUTHORITY, expected); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /solana/modules/token-router-sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "token-router-sdk" 3 | edition.workspace = true 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [features] 13 | testnet = ["token-router/testnet"] 14 | localnet = ["token-router/localnet"] 15 | 16 | [dependencies] 17 | token-router = { workspace = true, features = ["cpi"] } 18 | 19 | wormhole-io.workspace = true 20 | anchor-lang.workspace = true -------------------------------------------------------------------------------- /solana/modules/token-router-sdk/src/accounts/mod.rs: -------------------------------------------------------------------------------- 1 | mod prepared_fill; 2 | pub use prepared_fill::*; 3 | -------------------------------------------------------------------------------- /solana/modules/token-router-sdk/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod accounts; 2 | 3 | pub use token_router::cpi::*; 4 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "matching-engine" 3 | description = "Example Matching Engine Program" 4 | version.workspace = true 5 | edition.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | 14 | [features] 15 | default = ["no-idl"] 16 | no-entrypoint = [] 17 | no-idl = [] 18 | no-log-ix-name = [] 19 | cpi = ["no-entrypoint"] 20 | mainnet = ["common/mainnet"] 21 | testnet = ["common/testnet"] 22 | localnet = ["common/localnet"] 23 | integration-test = ["localnet"] 24 | idl-build = [ 25 | "localnet", 26 | "common/idl-build", 27 | "anchor-lang/idl-build", 28 | "anchor-spl/idl-build" 29 | ] 30 | 31 | [dependencies] 32 | common.workspace = true 33 | wormhole-solana-utils.workspace = true 34 | 35 | anchor-lang = { workspace = true, features = ["event-cpi", "init-if-needed"] } 36 | anchor-spl.workspace = true 37 | solana-program.workspace = true 38 | 39 | hex.workspace = true 40 | ruint.workspace = true 41 | cfg-if.workspace = true 42 | 43 | [dev-dependencies] 44 | hex-literal.workspace = true 45 | 46 | [lints] 47 | workspace = true -------------------------------------------------------------------------------- /solana/programs/matching-engine/README.md: -------------------------------------------------------------------------------- 1 | # Matching Engine Program 2 | 3 | A program to facilitate the transfer of USDC between networks that allow Wormhole and CCTP bridging. 4 | With the help of solvers, allowing USDC to be transferred faster than finality. 5 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/auction_closed.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::state::Auction; 4 | 5 | #[event] 6 | #[derive(Debug)] 7 | pub struct AuctionClosed { 8 | pub auction: Auction, 9 | } 10 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/auction_settled.rs: -------------------------------------------------------------------------------- 1 | use crate::state::MessageProtocol; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Debug, AnchorSerialize, AnchorDeserialize)] 5 | pub struct SettledTokenAccountInfo { 6 | pub key: Pubkey, 7 | pub balance_after: u64, 8 | } 9 | 10 | #[event] 11 | #[derive(Debug)] 12 | pub struct AuctionSettled { 13 | /// The pubkey of the auction that was settled. 14 | pub fast_vaa_hash: [u8; 32], 15 | 16 | /// If there was an active auction, this field will have the pubkey of the best offer token that 17 | /// was paid back and its balance after repayment. 18 | pub best_offer_token: Option, 19 | 20 | /// Depending on whether there was an active auction, this field will have the pubkey of the 21 | /// base fee token account (if there was an auction) or fee recipient token (if there was no 22 | /// auction). 23 | pub base_fee_token: Option, 24 | 25 | /// This value will only be some if there was no active auction. 26 | pub with_execute: Option, 27 | } 28 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/auction_updated.rs: -------------------------------------------------------------------------------- 1 | use crate::state::MessageProtocol; 2 | use anchor_lang::prelude::*; 3 | 4 | #[event] 5 | #[derive(Debug)] 6 | pub struct AuctionUpdated { 7 | pub config_id: u32, 8 | pub fast_vaa_hash: [u8; 32], 9 | pub vaa: Option, 10 | pub source_chain: u16, 11 | pub target_protocol: MessageProtocol, 12 | pub redeemer_message_len: u16, 13 | 14 | pub end_slot: u64, 15 | pub best_offer_token: Pubkey, 16 | pub token_balance_before: u64, 17 | pub amount_in: u64, 18 | pub total_deposit: u64, 19 | pub max_offer_price_allowed: Option, 20 | } 21 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/enacted.rs: -------------------------------------------------------------------------------- 1 | use crate::state::ProposalAction; 2 | use anchor_lang::prelude::*; 3 | 4 | #[event] 5 | pub struct Enacted { 6 | pub action: ProposalAction, 7 | } 8 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/fast_fill_redeemed.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::state::FastFillSeeds; 4 | 5 | #[event] 6 | pub struct FastFillRedeemed { 7 | pub prepared_by: Pubkey, 8 | pub fast_fill: FastFillSeeds, 9 | } 10 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/fast_fill_sequence_reserved.rs: -------------------------------------------------------------------------------- 1 | use crate::state::FastFillSeeds; 2 | use anchor_lang::prelude::*; 3 | 4 | #[event] 5 | pub struct FastFillSequenceReserved { 6 | pub fast_vaa_hash: [u8; 32], 7 | pub fast_fill: FastFillSeeds, 8 | } 9 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/filled_local_fast_order.rs: -------------------------------------------------------------------------------- 1 | use crate::state::{FastFillInfo, FastFillSeeds}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[event] 5 | pub struct LocalFastOrderFilled { 6 | pub seeds: FastFillSeeds, 7 | pub info: FastFillInfo, 8 | pub auction: Option, 9 | } 10 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/mod.rs: -------------------------------------------------------------------------------- 1 | mod auction_closed; 2 | pub use auction_closed::*; 3 | 4 | mod auction_settled; 5 | pub use auction_settled::*; 6 | 7 | mod auction_updated; 8 | pub use auction_updated::*; 9 | 10 | mod enacted; 11 | pub use enacted::*; 12 | 13 | mod fast_fill_redeemed; 14 | pub use fast_fill_redeemed::*; 15 | 16 | mod fast_fill_sequence_reserved; 17 | pub use fast_fill_sequence_reserved::*; 18 | 19 | mod filled_local_fast_order; 20 | pub use filled_local_fast_order::*; 21 | 22 | mod order_executed; 23 | pub use order_executed::*; 24 | 25 | mod proposed; 26 | pub use proposed::*; 27 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/order_executed.rs: -------------------------------------------------------------------------------- 1 | use crate::state::MessageProtocol; 2 | use anchor_lang::prelude::*; 3 | 4 | #[event] 5 | #[derive(Debug)] 6 | pub struct OrderExecuted { 7 | pub fast_vaa_hash: [u8; 32], 8 | pub vaa: Pubkey, 9 | pub source_chain: u16, 10 | pub target_protocol: MessageProtocol, 11 | pub penalized: bool, 12 | } 13 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/events/proposed.rs: -------------------------------------------------------------------------------- 1 | use crate::state::ProposalAction; 2 | use anchor_lang::prelude::*; 3 | 4 | #[event] 5 | pub struct Proposed { 6 | pub action: ProposalAction, 7 | } 8 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/close_proposal.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, error::MatchingEngineError, state::Proposal}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct CloseProposal<'info> { 6 | admin: Admin<'info>, 7 | 8 | /// CHECK: This account must equal proposal.by pubkey. 9 | #[account( 10 | mut, 11 | address = proposal.by 12 | )] 13 | proposed_by: UncheckedAccount<'info>, 14 | 15 | #[account( 16 | mut, 17 | close = proposed_by, 18 | seeds = [ 19 | Proposal::SEED_PREFIX, 20 | &proposal.id.to_be_bytes(), 21 | ], 22 | bump = proposal.bump, 23 | constraint = proposal.slot_enacted_at.is_none() @ MatchingEngineError::ProposalAlreadyEnacted 24 | )] 25 | proposal: Account<'info, Proposal>, 26 | } 27 | 28 | pub fn close_proposal(_ctx: Context) -> Result<()> { 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/init_event.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::composite::*; 4 | 5 | #[derive(Accounts)] 6 | pub struct InitEventSubscription<'info> { 7 | #[account(mut)] 8 | payer: Signer<'info>, 9 | 10 | admin: OwnerOnly<'info>, 11 | 12 | /// This account will be serialized with an event, so its discriminator will change with every 13 | /// update. 14 | /// 15 | /// CHECK: Mutable, must have seeds \["event"\]. 16 | #[account( 17 | init, 18 | payer = payer, 19 | space = 10_240, 20 | seeds = [b"event"], 21 | bump, 22 | )] 23 | event_subscription: UncheckedAccount<'info>, 24 | 25 | system_program: Program<'info, System>, 26 | } 27 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/migrate.rs: -------------------------------------------------------------------------------- 1 | use crate::composite::*; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct Migrate<'info> { 6 | admin: OwnerOnly<'info>, 7 | } 8 | 9 | pub fn migrate(_ctx: Context) -> Result<()> { 10 | msg!("Nothing to migrate"); 11 | 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/mod.rs: -------------------------------------------------------------------------------- 1 | mod close_proposal; 2 | pub use close_proposal::*; 3 | 4 | mod initialize; 5 | pub use initialize::*; 6 | 7 | mod set_pause; 8 | pub use set_pause::*; 9 | 10 | mod migrate; 11 | pub use migrate::*; 12 | 13 | mod ownership_transfer_request; 14 | pub use ownership_transfer_request::*; 15 | 16 | mod propose; 17 | pub use propose::*; 18 | 19 | mod router_endpoint; 20 | pub use router_endpoint::*; 21 | 22 | mod update; 23 | pub use update::*; 24 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/cancel.rs: -------------------------------------------------------------------------------- 1 | use crate::composite::*; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct CancelOwnershipTransferRequest<'info> { 6 | admin: OwnerOnlyMut<'info>, 7 | } 8 | 9 | pub fn cancel_ownership_transfer_request( 10 | ctx: Context, 11 | ) -> Result<()> { 12 | common::admin::utils::pending_owner::cancel_transfer_ownership( 13 | &mut ctx.accounts.admin.custodian, 14 | ); 15 | 16 | // Done. 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/confirm.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::MatchingEngineError, state::Custodian}; 2 | use anchor_lang::prelude::*; 3 | use common::admin::utils::pending_owner; 4 | 5 | #[derive(Accounts)] 6 | pub struct ConfirmOwnershipTransferRequest<'info> { 7 | /// Must be the pending owner of the program set in the [`OwnerConfig`] 8 | /// account. 9 | pending_owner: Signer<'info>, 10 | 11 | #[account( 12 | mut, 13 | seeds = [Custodian::SEED_PREFIX], 14 | bump = Custodian::BUMP, 15 | constraint = { 16 | custodian.pending_owner.is_some() 17 | } @ MatchingEngineError::NoTransferOwnershipRequest, 18 | constraint = { 19 | pending_owner::only_pending_owner_unchecked(&custodian, &pending_owner.key()) 20 | } @ MatchingEngineError::NotPendingOwner, 21 | )] 22 | custodian: Account<'info, Custodian>, 23 | } 24 | 25 | pub fn confirm_ownership_transfer_request( 26 | ctx: Context, 27 | ) -> Result<()> { 28 | pending_owner::accept_ownership_unchecked(&mut ctx.accounts.custodian); 29 | 30 | // Done. 31 | Ok(()) 32 | } 33 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/mod.rs: -------------------------------------------------------------------------------- 1 | mod cancel; 2 | pub use cancel::*; 3 | 4 | mod confirm; 5 | pub use confirm::*; 6 | 7 | mod submit; 8 | pub use submit::*; 9 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/ownership_transfer_request/submit.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, error::MatchingEngineError}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct SubmitOwnershipTransferRequest<'info> { 6 | admin: OwnerOnlyMut<'info>, 7 | 8 | /// New Owner. 9 | /// 10 | /// CHECK: Must be neither zero pubkey nor current owner. 11 | #[account( 12 | constraint = new_owner.key() != Pubkey::default() @ MatchingEngineError::InvalidNewOwner, 13 | constraint = new_owner.key() != admin.owner.key() @ MatchingEngineError::AlreadyOwner 14 | )] 15 | new_owner: UncheckedAccount<'info>, 16 | } 17 | 18 | pub fn submit_ownership_transfer_request( 19 | ctx: Context, 20 | ) -> Result<()> { 21 | common::admin::utils::pending_owner::transfer_ownership( 22 | &mut ctx.accounts.admin.custodian, 23 | &ctx.accounts.new_owner.key(), 24 | ); 25 | 26 | // Done. 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/propose/auction_parameters.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | composite::*, 3 | error::MatchingEngineError, 4 | state::{AuctionParameters, Proposal, ProposalAction}, 5 | }; 6 | use anchor_lang::prelude::*; 7 | 8 | #[derive(Accounts)] 9 | #[event_cpi] 10 | pub struct ProposeAuctionParameters<'info> { 11 | #[account(mut)] 12 | payer: Signer<'info>, 13 | 14 | admin: Admin<'info>, 15 | 16 | #[account( 17 | init, 18 | payer = payer, 19 | space = 8 + Proposal::INIT_SPACE, 20 | seeds = [ 21 | Proposal::SEED_PREFIX, 22 | &admin.custodian.next_proposal_id.to_be_bytes() 23 | ], 24 | bump, 25 | )] 26 | proposal: Account<'info, Proposal>, 27 | 28 | system_program: Program<'info, System>, 29 | 30 | epoch_schedule: Sysvar<'info, EpochSchedule>, 31 | } 32 | 33 | pub fn propose_auction_parameters( 34 | ctx: Context, 35 | parameters: AuctionParameters, 36 | ) -> Result<()> { 37 | crate::utils::auction::require_valid_parameters(¶meters)?; 38 | 39 | let id = ctx 40 | .accounts 41 | .admin 42 | .custodian 43 | .auction_config_id 44 | .checked_add(1) 45 | .ok_or_else(|| MatchingEngineError::U32Overflow)?; 46 | let action = ProposalAction::UpdateAuctionParameters { id, parameters }; 47 | 48 | super::propose( 49 | super::Propose { 50 | custodian: &ctx.accounts.admin.custodian, 51 | proposal: &mut ctx.accounts.proposal, 52 | by: &ctx.accounts.admin.owner_or_assistant, 53 | epoch_schedule: &ctx.accounts.epoch_schedule, 54 | }, 55 | action, 56 | ctx.bumps.proposal, 57 | )?; 58 | 59 | // Emit event reflecting the proposal. 60 | emit_cpi!(crate::events::Proposed { action }); 61 | 62 | // Done. 63 | Ok(()) 64 | } 65 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/router_endpoint/add/local.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, state::RouterEndpoint, utils}; 2 | use anchor_lang::prelude::*; 3 | use common::wormhole_cctp_solana::wormhole::SOLANA_CHAIN; 4 | 5 | #[derive(Accounts)] 6 | pub struct AddLocalRouterEndpoint<'info> { 7 | #[account(mut)] 8 | payer: Signer<'info>, 9 | 10 | admin: Admin<'info>, 11 | 12 | #[account( 13 | init, 14 | payer = payer, 15 | space = 8 + RouterEndpoint::INIT_SPACE, 16 | seeds = [ 17 | RouterEndpoint::SEED_PREFIX, 18 | &SOLANA_CHAIN.to_be_bytes() 19 | ], 20 | bump, 21 | )] 22 | router_endpoint: Account<'info, RouterEndpoint>, 23 | 24 | local: LocalTokenRouter<'info>, 25 | 26 | system_program: Program<'info, System>, 27 | } 28 | 29 | pub fn add_local_router_endpoint(ctx: Context) -> Result<()> { 30 | utils::admin::handle_add_local_router_endpoint( 31 | &mut ctx.accounts.router_endpoint, 32 | &ctx.accounts.local.token_router_program, 33 | &ctx.accounts.local.token_router_emitter, 34 | &ctx.accounts.local.token_router_mint_recipient, 35 | ctx.bumps.router_endpoint.into(), 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/router_endpoint/add/mod.rs: -------------------------------------------------------------------------------- 1 | mod cctp; 2 | pub use cctp::*; 3 | 4 | mod local; 5 | pub use local::*; 6 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/router_endpoint/disable.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, state::MessageProtocol}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct DisableRouterEndpoint<'info> { 6 | admin: OwnerOnly<'info>, 7 | 8 | router_endpoint: ExistingMutRouterEndpoint<'info>, 9 | } 10 | 11 | pub fn disable_router_endpoint(ctx: Context) -> Result<()> { 12 | let endpoint = &mut ctx.accounts.router_endpoint.info; 13 | endpoint.protocol = MessageProtocol::None; 14 | endpoint.address = Default::default(); 15 | endpoint.mint_recipient = Default::default(); 16 | 17 | // Done. 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/router_endpoint/mod.rs: -------------------------------------------------------------------------------- 1 | mod add; 2 | pub use add::*; 3 | 4 | mod disable; 5 | pub use disable::*; 6 | 7 | mod update; 8 | pub use update::*; 9 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/router_endpoint/update/cctp.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | composite::*, 3 | utils::{self, admin::AddCctpRouterEndpointArgs}, 4 | }; 5 | use anchor_lang::prelude::*; 6 | use common::wormhole_cctp_solana::cctp::token_messenger_minter_program::{ 7 | self, RemoteTokenMessenger, 8 | }; 9 | 10 | #[derive(Accounts)] 11 | #[instruction(args: AddCctpRouterEndpointArgs)] 12 | pub struct UpdateCctpRouterEndpoint<'info> { 13 | admin: OwnerOnly<'info>, 14 | 15 | #[account( 16 | constraint = { 17 | require_eq!( 18 | args.chain, 19 | router_endpoint.chain, 20 | crate::error::MatchingEngineError::InvalidEndpoint, 21 | ); 22 | 23 | true 24 | } 25 | )] 26 | router_endpoint: ExistingMutRouterEndpoint<'info>, 27 | 28 | /// CHECK: Seeds must be \["remote_token_messenger"\, remote_domain.to_string()] (CCTP Token 29 | /// Messenger Minter program). 30 | #[account( 31 | seeds = [ 32 | RemoteTokenMessenger::SEED_PREFIX, 33 | args.cctp_domain.to_string().as_ref() 34 | ], 35 | bump, 36 | seeds::program = token_messenger_minter_program::id(), 37 | )] 38 | remote_token_messenger: Account<'info, RemoteTokenMessenger>, 39 | } 40 | 41 | pub fn update_cctp_router_endpoint( 42 | ctx: Context, 43 | args: AddCctpRouterEndpointArgs, 44 | ) -> Result<()> { 45 | utils::admin::handle_add_cctp_router_endpoint(&mut ctx.accounts.router_endpoint, args, None) 46 | } 47 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/router_endpoint/update/local.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, error::MatchingEngineError, utils}; 2 | use anchor_lang::prelude::*; 3 | use common::wormhole_cctp_solana::wormhole::SOLANA_CHAIN; 4 | 5 | #[derive(Accounts)] 6 | pub struct UpdateLocalRouterEndpoint<'info> { 7 | admin: OwnerOnly<'info>, 8 | 9 | #[account( 10 | constraint = { 11 | require_eq!( 12 | router_endpoint.chain, 13 | SOLANA_CHAIN, 14 | MatchingEngineError::InvalidChain 15 | ); 16 | true 17 | } 18 | )] 19 | router_endpoint: ExistingMutRouterEndpoint<'info>, 20 | 21 | local: LocalTokenRouter<'info>, 22 | } 23 | 24 | pub fn update_local_router_endpoint(ctx: Context) -> Result<()> { 25 | utils::admin::handle_add_local_router_endpoint( 26 | &mut ctx.accounts.router_endpoint, 27 | &ctx.accounts.local.token_router_program, 28 | &ctx.accounts.local.token_router_emitter, 29 | &ctx.accounts.local.token_router_mint_recipient, 30 | None, 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/router_endpoint/update/mod.rs: -------------------------------------------------------------------------------- 1 | mod cctp; 2 | pub use cctp::*; 3 | 4 | mod local; 5 | pub use local::*; 6 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/set_pause.rs: -------------------------------------------------------------------------------- 1 | use crate::composite::*; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct SetPause<'info> { 6 | admin: AdminMut<'info>, 7 | } 8 | 9 | pub fn set_pause(ctx: Context, paused: bool) -> Result<()> { 10 | let custodian = &mut ctx.accounts.admin.custodian; 11 | custodian.paused = paused; 12 | custodian.paused_set_by = ctx.accounts.admin.owner_or_assistant.key(); 13 | 14 | // Done. 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/update/fee_recipient_token.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, error::MatchingEngineError}; 2 | use anchor_lang::prelude::*; 3 | use anchor_spl::token; 4 | 5 | #[derive(Accounts)] 6 | pub struct UpdateFeeRecipient<'info> { 7 | admin: AdminMut<'info>, 8 | 9 | #[account( 10 | associated_token::mint = common::USDC_MINT, 11 | associated_token::authority = new_fee_recipient, 12 | )] 13 | new_fee_recipient_token: Account<'info, token::TokenAccount>, 14 | 15 | /// New Fee Recipient. 16 | /// 17 | /// CHECK: Must not be zero pubkey. 18 | #[account( 19 | constraint = { 20 | new_fee_recipient.key() != Pubkey::default() 21 | } @ MatchingEngineError::FeeRecipientZeroPubkey, 22 | )] 23 | new_fee_recipient: UncheckedAccount<'info>, 24 | } 25 | 26 | pub fn update_fee_recipient(ctx: Context) -> Result<()> { 27 | // Update the fee_recipient key. 28 | ctx.accounts.admin.custodian.fee_recipient_token = ctx.accounts.new_fee_recipient_token.key(); 29 | 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/update/mod.rs: -------------------------------------------------------------------------------- 1 | mod auction_parameters; 2 | pub use auction_parameters::*; 3 | 4 | mod fee_recipient_token; 5 | pub use fee_recipient_token::*; 6 | 7 | mod owner_assistant; 8 | pub use owner_assistant::*; 9 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/admin/update/owner_assistant.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, error::MatchingEngineError}; 2 | use anchor_lang::prelude::*; 3 | use common::admin::utils::assistant; 4 | 5 | #[derive(Accounts)] 6 | pub struct UpdateOwnerAssistant<'info> { 7 | admin: OwnerOnlyMut<'info>, 8 | 9 | /// New Assistant. 10 | /// 11 | /// CHECK: Must not be zero pubkey. 12 | #[account( 13 | constraint = { 14 | new_owner_assistant.key() != Pubkey::default() 15 | } @ MatchingEngineError::AssistantZeroPubkey, 16 | )] 17 | new_owner_assistant: UncheckedAccount<'info>, 18 | } 19 | 20 | pub fn update_owner_assistant(ctx: Context) -> Result<()> { 21 | assistant::transfer_owner_assistant( 22 | &mut ctx.accounts.admin.custodian, 23 | &ctx.accounts.new_owner_assistant, 24 | ); 25 | 26 | // Done. 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/auction/close.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Deref; 2 | 3 | use crate::{ 4 | error::MatchingEngineError, 5 | state::{Auction, AuctionStatus}, 6 | }; 7 | use anchor_lang::prelude::*; 8 | 9 | #[derive(Accounts)] 10 | #[event_cpi] 11 | pub struct CloseAuction<'info> { 12 | #[account( 13 | mut, 14 | close = beneficiary, 15 | constraint = { 16 | require!( 17 | matches!(auction.status, AuctionStatus::Settled {..}), 18 | MatchingEngineError::AuctionNotSettled, 19 | ); 20 | 21 | let expiration = 22 | i64::from(auction.vaa_timestamp).saturating_add(crate::VAA_AUCTION_EXPIRATION_TIME); 23 | require!( 24 | Clock::get().unwrap().unix_timestamp >= expiration, 25 | MatchingEngineError::CannotCloseAuctionYet, 26 | ); 27 | 28 | true 29 | } 30 | )] 31 | auction: Account<'info, Auction>, 32 | 33 | /// CHECK: This account is whoever originally created the auction account (see 34 | /// [Auction::prepared_by]. 35 | #[account( 36 | mut, 37 | address = auction.prepared_by, 38 | )] 39 | beneficiary: UncheckedAccount<'info>, 40 | } 41 | 42 | pub fn close_auction(ctx: Context) -> Result<()> { 43 | emit_cpi!(crate::events::AuctionClosed { 44 | auction: ctx.accounts.auction.deref().clone(), 45 | }); 46 | 47 | Ok(()) 48 | } 49 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/auction/mod.rs: -------------------------------------------------------------------------------- 1 | mod close; 2 | pub use close::*; 3 | 4 | mod execute_fast_order; 5 | pub use execute_fast_order::*; 6 | 7 | mod offer; 8 | pub use offer::*; 9 | 10 | mod prepare_order_response; 11 | pub use prepare_order_response::*; 12 | 13 | mod settle; 14 | pub use settle::*; 15 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/auction/offer/mod.rs: -------------------------------------------------------------------------------- 1 | mod improve; 2 | pub use improve::*; 3 | 4 | mod place_initial; 5 | pub use place_initial::*; 6 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/auction/offer/place_initial/mod.rs: -------------------------------------------------------------------------------- 1 | mod cctp; 2 | pub use cctp::*; 3 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/auction/prepare_order_response/mod.rs: -------------------------------------------------------------------------------- 1 | mod cctp; 2 | pub use cctp::*; 3 | 4 | use anchor_lang::prelude::*; 5 | 6 | fn prepare_order_response_noop() -> Result<()> { 7 | msg!("Already prepared"); 8 | 9 | // No-op. 10 | Ok(()) 11 | } 12 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/auction/settle/mod.rs: -------------------------------------------------------------------------------- 1 | mod complete; 2 | pub use complete::*; 3 | 4 | mod none; 5 | pub use none::*; 6 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/fast_fill/close_redeemed.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::MatchingEngineError, state::FastFill}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct CloseRedeemedFastFill<'info> { 6 | /// Instead of having the preparer sign for this instruction, we allow anyone to call this 7 | /// instruction on behalf of the preparer. 8 | /// 9 | /// CHECK: Must equal the `prepared_by` field of the `fast_fill` account. 10 | #[account( 11 | mut, 12 | address = fast_fill.info.prepared_by, 13 | )] 14 | prepared_by: UncheckedAccount<'info>, 15 | 16 | #[account( 17 | mut, 18 | close = prepared_by, 19 | constraint = fast_fill.redeemed @ MatchingEngineError::FastFillNotRedeemed, 20 | )] 21 | fast_fill: Account<'info, FastFill>, 22 | } 23 | 24 | pub fn close_redeemed_fast_fill(_ctx: Context) -> Result<()> { 25 | Ok(()) 26 | } 27 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/fast_fill/mod.rs: -------------------------------------------------------------------------------- 1 | mod close_redeemed; 2 | pub use close_redeemed::*; 3 | 4 | mod complete; 5 | pub use complete::*; 6 | 7 | mod reserve_sequence; 8 | pub use reserve_sequence::*; 9 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/active_auction.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, error::MatchingEngineError, state::AuctionConfig}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | #[event_cpi] 6 | pub struct ReserveFastFillSequenceActiveAuction<'info> { 7 | reserve_sequence: ReserveFastFillSequence<'info>, 8 | 9 | #[account( 10 | constraint = match &reserve_sequence.auction.info { 11 | Some(info) => { 12 | // Verify that the auction period has expired. 13 | require_eq!( 14 | info.config_id, 15 | auction_config.id, 16 | MatchingEngineError::AuctionConfigMismatch 17 | ); 18 | require!( 19 | !info.within_auction_duration(&auction_config), 20 | MatchingEngineError::AuctionPeriodNotExpired 21 | ); 22 | 23 | true 24 | 25 | }, 26 | _ => return err!(MatchingEngineError::NoAuction), 27 | } 28 | )] 29 | auction_config: Account<'info, AuctionConfig>, 30 | } 31 | 32 | pub fn reserve_fast_fill_sequence_active_auction( 33 | ctx: Context, 34 | ) -> Result<()> { 35 | let beneficiary = ctx.accounts.reserve_sequence.payer.key(); 36 | let fast_vaa_hash = ctx.accounts.reserve_sequence.auction.vaa_hash; 37 | 38 | let sequence_reserved_event = super::set_reserved_sequence_data( 39 | &mut ctx.accounts.reserve_sequence, 40 | &ctx.bumps.reserve_sequence, 41 | fast_vaa_hash, 42 | beneficiary, 43 | )?; 44 | 45 | // Emit an event indicating that the fast fill sequence has been reserved. 46 | emit_cpi!(sequence_reserved_event); 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/fast_fill/reserve_sequence/no_auction.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, error::MatchingEngineError, state::PreparedOrderResponse}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | #[event_cpi] 6 | pub struct ReserveFastFillSequenceNoAuction<'info> { 7 | #[account( 8 | constraint = reserve_sequence.auction.info.is_none() @ MatchingEngineError::AuctionExists, 9 | )] 10 | reserve_sequence: ReserveFastFillSequence<'info>, 11 | 12 | /// The preparer will be the beneficiary of the reserved fast fill sequence account when it is 13 | /// closed. This instruction will not allow this account to be provided if there is an existing 14 | /// auction, which would enforce the order be executed when it is time to complete the auction. 15 | #[account( 16 | constraint = { 17 | // Check association with fast order path. 18 | require!( 19 | prepared_order_response.seeds.fast_vaa_hash 20 | == reserve_sequence.fast_order_path.fast_vaa.load_unchecked().digest().0, 21 | MatchingEngineError::VaaMismatch 22 | ); 23 | 24 | true 25 | } 26 | )] 27 | prepared_order_response: Account<'info, PreparedOrderResponse>, 28 | } 29 | 30 | pub fn reserve_fast_fill_sequence_no_auction( 31 | ctx: Context, 32 | ) -> Result<()> { 33 | let prepared_order_response = &ctx.accounts.prepared_order_response; 34 | 35 | ctx.accounts.reserve_sequence.auction.set_inner( 36 | prepared_order_response.new_auction_placeholder(ctx.bumps.reserve_sequence.auction), 37 | ); 38 | 39 | let sequence_reserved_event = super::set_reserved_sequence_data( 40 | &mut ctx.accounts.reserve_sequence, 41 | &ctx.bumps.reserve_sequence, 42 | prepared_order_response.seeds.fast_vaa_hash, 43 | prepared_order_response.prepared_by, 44 | )?; 45 | 46 | // Emit an event indicating that the fast fill sequence has been reserved. 47 | emit_cpi!(sequence_reserved_event); 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/processor/mod.rs: -------------------------------------------------------------------------------- 1 | mod admin; 2 | pub use admin::*; 3 | 4 | mod auction; 5 | pub use auction::*; 6 | 7 | mod fast_fill; 8 | pub use fast_fill::*; 9 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/state/auction_config.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[derive(Debug, AnchorSerialize, AnchorDeserialize, InitSpace, Clone, Copy, PartialEq, Eq)] 4 | pub struct AuctionParameters { 5 | // The percentage of the penalty that is awarded to the user when the auction is completed. 6 | pub user_penalty_reward_bps: u32, 7 | 8 | // The initial penalty percentage that is incurred once the grace period is over. 9 | pub initial_penalty_bps: u32, 10 | 11 | // The duration of the auction in slots. About 500ms on Solana. 12 | pub duration: u16, 13 | 14 | /** 15 | * The grace period of the auction in slots. This is the number of slots the highest bidder 16 | * has to execute the fast order before incurring a penalty. About 15 seconds on Avalanche. 17 | * This value INCLUDES the `_auctionDuration`. 18 | */ 19 | pub grace_period: u16, 20 | 21 | // The `securityDeposit` decays over the `penalty_slots` slots period. 22 | pub penalty_period: u16, 23 | 24 | // The minimum offer increment in percentage terms. 25 | pub min_offer_delta_bps: u32, 26 | 27 | /// The base security deposit, which will the the additional amount an auction participant must 28 | /// deposit to participate in an auction. 29 | pub security_deposit_base: u64, 30 | 31 | /// Additional security deposit based on the notional of the order amount. 32 | pub security_deposit_bps: u32, 33 | } 34 | 35 | #[account] 36 | #[derive(Debug, InitSpace, Copy)] 37 | pub struct AuctionConfig { 38 | /// Monotonically increasing identifier for auction configs. 39 | pub id: u32, 40 | /// Auction parameters, which are validated by [crate::utils::auction::require_valid_parameters]. 41 | pub parameters: AuctionParameters, 42 | } 43 | 44 | impl AuctionConfig { 45 | pub const SEED_PREFIX: &'static [u8] = b"auction-config"; 46 | } 47 | 48 | impl std::ops::Deref for AuctionConfig { 49 | type Target = AuctionParameters; 50 | 51 | fn deref(&self) -> &Self::Target { 52 | &self.parameters 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/state/fast_fill/reserved_sequence.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use super::FastFillSeeds; 4 | 5 | #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, InitSpace)] 6 | pub struct ReservedFastFillSequenceSeeds { 7 | pub fast_vaa_hash: [u8; 32], 8 | pub bump: u8, 9 | } 10 | 11 | #[account] 12 | #[derive(Debug, InitSpace)] 13 | pub struct ReservedFastFillSequence { 14 | pub seeds: ReservedFastFillSequenceSeeds, 15 | pub beneficiary: Pubkey, 16 | pub fast_fill_seeds: FastFillSeeds, 17 | } 18 | 19 | impl ReservedFastFillSequence { 20 | pub const SEED_PREFIX: &'static [u8] = b"reserved-fast-fill-sequence"; 21 | } 22 | 23 | impl std::ops::Deref for ReservedFastFillSequence { 24 | type Target = FastFillSeeds; 25 | 26 | fn deref(&self) -> &Self::Target { 27 | &self.fast_fill_seeds 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/state/fast_fill/sequencer.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[derive( 4 | Debug, AnchorSerialize, AnchorDeserialize, Clone, Copy, InitSpace, Default, PartialEq, Eq, 5 | )] 6 | pub struct FastFillSequencerSeeds { 7 | pub source_chain: u16, 8 | pub sender: [u8; 32], 9 | pub bump: u8, 10 | } 11 | 12 | #[account] 13 | #[derive(Debug, InitSpace)] 14 | pub struct FastFillSequencer { 15 | pub seeds: FastFillSequencerSeeds, 16 | pub next_sequence: u64, 17 | } 18 | 19 | impl FastFillSequencer { 20 | pub const SEED_PREFIX: &'static [u8] = b"fast-fill-sequencer"; 21 | } 22 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod auction; 2 | pub use auction::*; 3 | 4 | mod auction_config; 5 | pub use auction_config::*; 6 | 7 | mod auction_history; 8 | pub use auction_history::*; 9 | 10 | mod custodian; 11 | pub use custodian::*; 12 | 13 | mod fast_fill; 14 | pub use fast_fill::*; 15 | 16 | mod prepared_order_response; 17 | pub use prepared_order_response::*; 18 | 19 | mod proposal; 20 | pub use proposal::*; 21 | 22 | pub(crate) mod router_endpoint; 23 | pub use router_endpoint::*; 24 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/state/proposal.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | use crate::AuctionParameters; 4 | 5 | #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, InitSpace, PartialEq, Eq, Copy)] 6 | pub enum ProposalAction { 7 | None, 8 | UpdateAuctionParameters { 9 | id: u32, 10 | parameters: AuctionParameters, 11 | }, 12 | } 13 | 14 | #[account] 15 | #[derive(Debug, InitSpace)] 16 | pub struct Proposal { 17 | pub id: u64, 18 | pub bump: u8, 19 | 20 | pub action: ProposalAction, 21 | pub by: Pubkey, 22 | pub owner: Pubkey, 23 | 24 | pub slot_proposed_at: u64, 25 | pub slot_enact_delay: u64, 26 | pub slot_enacted_at: Option, 27 | } 28 | 29 | impl Proposal { 30 | pub const SEED_PREFIX: &'static [u8] = b"proposal"; 31 | } 32 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/state/router_endpoint.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | /// Protocol used to transfer assets. 4 | #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, PartialEq, Eq, InitSpace, Copy)] 5 | pub enum MessageProtocol { 6 | /// Unassigned or disabled. 7 | None, 8 | /// Tokens destined for this network (Solana). 9 | Local { program_id: Pubkey }, 10 | /// Tokens to be burned and minted via Circle's CCTP protocol. 11 | Cctp { 12 | /// CCTP domain, which is how CCTP registers identifies foreign networks. 13 | domain: u32, 14 | }, 15 | } 16 | 17 | impl std::fmt::Display for MessageProtocol { 18 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 19 | match self { 20 | MessageProtocol::None => write!(f, "None"), 21 | MessageProtocol::Local { program_id } => { 22 | write!(f, "Local {{ program_id: {} }}", program_id) 23 | } 24 | MessageProtocol::Cctp { domain } => write!(f, "Cctp {{ domain: {} }}", domain), 25 | } 26 | } 27 | } 28 | 29 | #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, Copy, InitSpace)] 30 | pub struct EndpointInfo { 31 | /// Emitter chain. Cannot equal `1` (Solana's Chain ID). 32 | pub chain: u16, 33 | 34 | /// Emitter address. Cannot be zero address. 35 | pub address: [u8; 32], 36 | 37 | /// Future-proof field in case another network has token accounts to send assets to instead of 38 | /// sending to the address directly. 39 | pub mint_recipient: [u8; 32], 40 | 41 | /// Specific message protocol used to move assets. 42 | pub protocol: MessageProtocol, 43 | } 44 | 45 | #[account] 46 | #[derive(Debug, InitSpace)] 47 | /// Foreign emitter account data. 48 | pub struct RouterEndpoint { 49 | pub bump: u8, 50 | pub info: EndpointInfo, 51 | } 52 | 53 | impl std::ops::Deref for RouterEndpoint { 54 | type Target = EndpointInfo; 55 | 56 | fn deref(&self) -> &Self::Target { 57 | &self.info 58 | } 59 | } 60 | 61 | impl RouterEndpoint { 62 | pub const SEED_PREFIX: &'static [u8] = b"endpoint"; 63 | } 64 | -------------------------------------------------------------------------------- /solana/programs/matching-engine/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod admin; 2 | 3 | pub mod auction; 4 | 5 | use crate::{error::MatchingEngineError, state::RouterEndpoint}; 6 | use anchor_lang::prelude::*; 7 | use anchor_spl::token; 8 | use common::wormhole_cctp_solana::wormhole::{VaaAccount, SOLANA_CHAIN}; 9 | 10 | pub trait VaaDigest { 11 | fn digest(&self) -> [u8; 32]; 12 | } 13 | 14 | #[derive(PartialEq, Eq)] 15 | struct WrappedHash([u8; 32]); 16 | 17 | impl std::fmt::Display for WrappedHash { 18 | fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 19 | write!(f, "0x{}", hex::encode(self.0)) 20 | } 21 | } 22 | 23 | pub fn require_vaa_hash_equals(ctx: &A, vaa: &VaaAccount) -> Result 24 | where 25 | A: VaaDigest, 26 | { 27 | require_eq!( 28 | WrappedHash(vaa.digest().0), 29 | WrappedHash(ctx.digest()), 30 | MatchingEngineError::InvalidVaa 31 | ); 32 | Ok(true) 33 | } 34 | 35 | pub fn require_local_endpoint(endpoint: &RouterEndpoint) -> Result { 36 | require_eq!( 37 | endpoint.chain, 38 | SOLANA_CHAIN, 39 | MatchingEngineError::InvalidEndpoint 40 | ); 41 | 42 | Ok(true) 43 | } 44 | 45 | pub fn checked_deserialize_token_account( 46 | acc_info: &AccountInfo, 47 | expected_mint: &Pubkey, 48 | ) -> Option> { 49 | if acc_info.owner != &token::ID { 50 | None 51 | } else { 52 | let data = acc_info.try_borrow_data().ok()?; 53 | 54 | token::TokenAccount::try_deserialize(&mut &data[..]) 55 | .ok() 56 | .filter(|token_data| &token_data.mint == expected_mint && !token_data.is_frozen()) 57 | .map(Box::new) 58 | } 59 | } 60 | 61 | /// This method is just used out of convenience to return the same event that was emitted so 62 | /// something like `emit_cpi!` can be used on the same event. 63 | pub fn log_emit(event: E) -> E 64 | where 65 | E: anchor_lang::Event, 66 | { 67 | emit!(event); 68 | event 69 | } 70 | -------------------------------------------------------------------------------- /solana/programs/token-router/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "token-router" 3 | description = "Example Token Router Program" 4 | version.workspace = true 5 | edition.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | 14 | [features] 15 | default = ["no-idl"] 16 | no-entrypoint = [] 17 | no-idl = [] 18 | no-log-ix-name = [] 19 | cpi = ["no-entrypoint"] 20 | mainnet = [ 21 | "common/mainnet", 22 | "matching-engine/mainnet" 23 | ] 24 | testnet = [ 25 | "common/testnet", 26 | "matching-engine/testnet" 27 | ] 28 | localnet = [ 29 | "common/localnet", 30 | "matching-engine/localnet" 31 | ] 32 | integration-test = ["localnet"] 33 | idl-build = [ 34 | "localnet", 35 | "common/idl-build", 36 | "matching-engine/idl-build", 37 | "anchor-lang/idl-build", 38 | "anchor-spl/idl-build" 39 | ] 40 | 41 | [dependencies] 42 | common.workspace = true 43 | matching-engine = { workspace = true, features = ["cpi"] } 44 | wormhole-solana-utils.workspace = true 45 | 46 | anchor-lang = { workspace = true, features = ["derive", "init-if-needed"] } 47 | anchor-spl.workspace = true 48 | solana-program.workspace = true 49 | 50 | hex.workspace = true 51 | ruint.workspace = true 52 | cfg-if.workspace = true 53 | 54 | [dev-dependencies] 55 | hex-literal.workspace = true 56 | 57 | [lints] 58 | workspace = true -------------------------------------------------------------------------------- /solana/programs/token-router/README.md: -------------------------------------------------------------------------------- 1 | # Token Router Program 2 | 3 | A program which is used to send and receive USDC from other networks via CCTP accompanied by a 4 | Wormhole message. 5 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/error.rs: -------------------------------------------------------------------------------- 1 | #[anchor_lang::error_code] 2 | pub enum TokenRouterError { 3 | OwnerOnly = 0x2, 4 | OwnerOrAssistantOnly = 0x4, 5 | 6 | U64Overflow = 0x10, 7 | 8 | InvalidVaa = 0x30, 9 | 10 | InvalidDepositMessage = 0x44, 11 | InvalidPayloadId = 0x46, 12 | InvalidDepositPayloadId = 0x48, 13 | RedeemerMessageTooLarge = 0x4e, 14 | 15 | InvalidSourceRouter = 0x60, 16 | InvalidTargetRouter = 0x62, 17 | EndpointDisabled = 0x64, 18 | InvalidCctpEndpoint = 0x66, 19 | 20 | Paused = 0x80, 21 | 22 | AssistantZeroPubkey = 0x100, 23 | ImmutableProgram = 0x102, 24 | 25 | InvalidNewOwner = 0x202, 26 | AlreadyOwner = 0x204, 27 | NoTransferOwnershipRequest = 0x206, 28 | NotPendingOwner = 0x208, 29 | EitherSenderOrProgramTransferAuthority = 0x20a, 30 | DelegatedAmountMismatch = 0x20c, 31 | 32 | InsufficientAmount = 0x400, 33 | MinAmountOutTooHigh = 0x402, 34 | InvalidRedeemer = 0x404, 35 | PreparedFillTooLarge = 0x406, 36 | } 37 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/admin/migrate.rs: -------------------------------------------------------------------------------- 1 | use crate::composite::*; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct Migrate<'info> { 6 | admin: OwnerOnly<'info>, 7 | } 8 | 9 | pub fn migrate(_ctx: Context) -> Result<()> { 10 | msg!("Nothing to migrate"); 11 | 12 | Ok(()) 13 | } 14 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/admin/mod.rs: -------------------------------------------------------------------------------- 1 | // mod authorize_upgrade; 2 | // pub use authorize_upgrade::*; 3 | 4 | mod initialize; 5 | pub use initialize::*; 6 | 7 | mod migrate; 8 | pub use migrate::*; 9 | 10 | mod ownership_transfer_request; 11 | pub use ownership_transfer_request::*; 12 | 13 | mod set_pause; 14 | pub use set_pause::*; 15 | 16 | mod update; 17 | pub use update::*; 18 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/admin/ownership_transfer_request/cancel.rs: -------------------------------------------------------------------------------- 1 | use crate::composite::*; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct CancelOwnershipTransferRequest<'info> { 6 | admin: OwnerOnlyMut<'info>, 7 | } 8 | 9 | pub fn cancel_ownership_transfer_request( 10 | ctx: Context, 11 | ) -> Result<()> { 12 | common::admin::utils::pending_owner::cancel_transfer_ownership( 13 | &mut ctx.accounts.admin.custodian, 14 | ); 15 | 16 | // Done. 17 | Ok(()) 18 | } 19 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/admin/ownership_transfer_request/confirm.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::TokenRouterError, state::Custodian}; 2 | use anchor_lang::prelude::*; 3 | use common::admin::utils::pending_owner; 4 | #[derive(Accounts)] 5 | pub struct ConfirmOwnershipTransferRequest<'info> { 6 | /// Must be the pending owner of the program set in the [`OwnerConfig`] 7 | /// account. 8 | pending_owner: Signer<'info>, 9 | 10 | #[account( 11 | mut, 12 | seeds = [Custodian::SEED_PREFIX], 13 | bump = Custodian::BUMP, 14 | constraint = { 15 | custodian.pending_owner.is_some() 16 | } @ TokenRouterError::NoTransferOwnershipRequest, 17 | constraint = { 18 | pending_owner::only_pending_owner_unchecked(&custodian, &pending_owner.key()) 19 | } @ TokenRouterError::NotPendingOwner, 20 | )] 21 | custodian: Account<'info, Custodian>, 22 | } 23 | 24 | pub fn confirm_ownership_transfer_request( 25 | ctx: Context, 26 | ) -> Result<()> { 27 | pending_owner::accept_ownership_unchecked(&mut ctx.accounts.custodian); 28 | 29 | // Done. 30 | Ok(()) 31 | } 32 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/admin/ownership_transfer_request/mod.rs: -------------------------------------------------------------------------------- 1 | mod cancel; 2 | pub use cancel::*; 3 | 4 | mod confirm; 5 | pub use confirm::*; 6 | 7 | mod submit; 8 | pub use submit::*; 9 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/admin/ownership_transfer_request/submit.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, error::TokenRouterError}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct SubmitOwnershipTransferRequest<'info> { 6 | admin: OwnerOnlyMut<'info>, 7 | 8 | /// New Owner. 9 | /// 10 | /// CHECK: Must be neither zero pubkey nor current owner. 11 | #[account( 12 | constraint = new_owner.key() != Pubkey::default() @ TokenRouterError::InvalidNewOwner, 13 | constraint = new_owner.key() != admin.owner.key() @ TokenRouterError::AlreadyOwner 14 | )] 15 | new_owner: UncheckedAccount<'info>, 16 | } 17 | 18 | pub fn submit_ownership_transfer_request( 19 | ctx: Context, 20 | ) -> Result<()> { 21 | common::admin::utils::pending_owner::transfer_ownership( 22 | &mut ctx.accounts.admin.custodian, 23 | &ctx.accounts.new_owner.key(), 24 | ); 25 | 26 | // Done. 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/admin/set_pause.rs: -------------------------------------------------------------------------------- 1 | use crate::composite::*; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct SetPause<'info> { 6 | admin: AdminMut<'info>, 7 | } 8 | 9 | pub fn set_pause(ctx: Context, paused: bool) -> Result<()> { 10 | let custodian = &mut ctx.accounts.admin.custodian; 11 | custodian.paused = paused; 12 | custodian.paused_set_by = ctx.accounts.admin.owner_or_assistant.key(); 13 | 14 | // Done. 15 | Ok(()) 16 | } 17 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/admin/update/mod.rs: -------------------------------------------------------------------------------- 1 | mod owner_assistant; 2 | pub use owner_assistant::*; 3 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/admin/update/owner_assistant.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, error::TokenRouterError}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct UpdateOwnerAssistant<'info> { 6 | admin: OwnerOnlyMut<'info>, 7 | 8 | /// New Assistant. 9 | /// 10 | /// CHECK: Must not be zero pubkey. 11 | #[account( 12 | constraint = { 13 | new_owner_assistant.key() != Pubkey::default() 14 | } @ TokenRouterError::AssistantZeroPubkey, 15 | )] 16 | new_owner_assistant: UncheckedAccount<'info>, 17 | } 18 | 19 | pub fn update_owner_assistant(ctx: Context) -> Result<()> { 20 | common::admin::utils::assistant::transfer_owner_assistant( 21 | &mut ctx.accounts.admin.custodian, 22 | &ctx.accounts.new_owner_assistant, 23 | ); 24 | 25 | // Done. 26 | Ok(()) 27 | } 28 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/market_order/mod.rs: -------------------------------------------------------------------------------- 1 | mod place_cctp; 2 | pub use place_cctp::*; 3 | 4 | mod prepare; 5 | pub use prepare::*; 6 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/mod.rs: -------------------------------------------------------------------------------- 1 | mod admin; 2 | pub use admin::*; 3 | 4 | mod close_prepared_order; 5 | pub use close_prepared_order::*; 6 | 7 | mod consume_prepared_fill; 8 | pub use consume_prepared_fill::*; 9 | 10 | mod market_order; 11 | pub use market_order::*; 12 | 13 | mod redeem_fill; 14 | pub use redeem_fill::*; 15 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/processor/redeem_fill/mod.rs: -------------------------------------------------------------------------------- 1 | mod cctp; 2 | pub use cctp::*; 3 | 4 | mod fast; 5 | pub use fast::*; 6 | 7 | use anchor_lang::prelude::*; 8 | 9 | fn redeem_fill_noop() -> Result<()> { 10 | msg!("Already redeemed"); 11 | 12 | // No-op. 13 | Ok(()) 14 | } 15 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod custodian; 2 | pub use custodian::*; 3 | 4 | mod prepared_fill; 5 | pub use prepared_fill::*; 6 | 7 | mod prepared_order; 8 | pub use prepared_order::*; 9 | -------------------------------------------------------------------------------- /solana/programs/token-router/src/state/prepared_order.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, InitSpace)] 4 | pub enum OrderType { 5 | Market { min_amount_out: Option }, 6 | } 7 | 8 | #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, InitSpace)] 9 | pub struct PreparedOrderInfo { 10 | pub prepared_custody_token_bump: u8, 11 | 12 | pub order_sender: Pubkey, 13 | pub prepared_by: Pubkey, 14 | 15 | pub order_type: OrderType, 16 | pub src_token: Pubkey, 17 | pub refund_token: Pubkey, 18 | 19 | pub target_chain: u16, 20 | pub redeemer: [u8; 32], 21 | } 22 | 23 | #[account] 24 | #[derive(Debug)] 25 | pub struct PreparedOrder { 26 | pub info: PreparedOrderInfo, 27 | pub redeemer_message: Vec, 28 | } 29 | 30 | impl PreparedOrder { 31 | pub(crate) fn compute_size(redeemer_message_len: usize) -> usize { 32 | const FIXED: usize = 8 // DISCRIMINATOR 33 | + PreparedOrderInfo::INIT_SPACE 34 | + 4 // redeemer_message_len 35 | ; 36 | 37 | redeemer_message_len.saturating_add(FIXED) 38 | } 39 | } 40 | 41 | impl std::ops::Deref for PreparedOrder { 42 | type Target = PreparedOrderInfo; 43 | 44 | fn deref(&self) -> &Self::Target { 45 | &self.info 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "upgrade-manager" 3 | description = "Example Liquidity Layer Upgrade Manager" 4 | version.workspace = true 5 | edition.workspace = true 6 | authors.workspace = true 7 | license.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [lib] 12 | crate-type = ["cdylib", "lib"] 13 | 14 | [features] 15 | default = ["no-idl"] 16 | no-entrypoint = [] 17 | no-idl = [] 18 | no-log-ix-name = [] 19 | cpi = ["no-entrypoint"] 20 | mainnet = [ 21 | "common/mainnet", 22 | "matching-engine/mainnet", 23 | "token-router/mainnet" 24 | ] 25 | testnet = [ 26 | "common/testnet", 27 | "matching-engine/testnet", 28 | "token-router/testnet" 29 | ] 30 | localnet = [ 31 | "common/localnet", 32 | "matching-engine/localnet", 33 | "token-router/localnet" 34 | ] 35 | integration-test = ["localnet"] 36 | idl-build = [ 37 | "localnet", 38 | "common/idl-build", 39 | "matching-engine/idl-build", 40 | "token-router/idl-build", 41 | "anchor-lang/idl-build" 42 | ] 43 | 44 | [dependencies] 45 | common.workspace = true 46 | wormhole-solana-utils.workspace = true 47 | 48 | matching-engine = { workspace = true, features = ["cpi"] } 49 | token-router = { workspace = true, features = ["cpi"] } 50 | 51 | anchor-lang = { workspace = true, features = ["derive"] } 52 | solana-program.workspace = true 53 | 54 | hex.workspace = true 55 | cfg-if.workspace = true 56 | 57 | [dev-dependencies] 58 | hex-literal.workspace = true 59 | 60 | [lints] 61 | workspace = true -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/README.md: -------------------------------------------------------------------------------- 1 | # Upgrade Manager Program 2 | 3 | This program is used to perform upgrades for the Matching Engine and Token Router programs. 4 | 5 | Only the owner of these programs can perform these upgrades. 6 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/src/error.rs: -------------------------------------------------------------------------------- 1 | #[anchor_lang::error_code] 2 | pub enum UpgradeManagerError { 3 | NotUpgraded = 0x10, 4 | ProgramDataMismatch = 0x12, 5 | OwnerMismatch = 0x14, 6 | } 7 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![allow(clippy::result_large_err)] 3 | 4 | mod composite; 5 | 6 | mod error; 7 | 8 | mod processor; 9 | use processor::*; 10 | 11 | pub mod state; 12 | 13 | mod utils; 14 | 15 | use anchor_lang::prelude::*; 16 | 17 | declare_id!(common::UPGRADE_MANAGER_PROGRAM_ID); 18 | 19 | cfg_if::cfg_if! { 20 | if #[cfg(feature = "mainnet")] { 21 | const UPGRADE_AUTHORITY_BUMP: u8 = 255; 22 | } else if #[cfg(feature = "testnet")] { 23 | const UPGRADE_AUTHORITY_BUMP: u8 = 255; 24 | } else if #[cfg(feature = "localnet")] { 25 | const UPGRADE_AUTHORITY_BUMP: u8 = 255; 26 | } 27 | } 28 | 29 | const UPGRADE_AUTHORITY_SEED_PREFIX: &[u8] = b"upgrade"; 30 | const UPGRADE_AUTHORITY_SIGNER_SEEDS: &[&[u8]] = 31 | &[UPGRADE_AUTHORITY_SEED_PREFIX, &[UPGRADE_AUTHORITY_BUMP]]; 32 | 33 | #[program] 34 | pub mod upgrade_manager { 35 | use super::*; 36 | 37 | // Matching Engine 38 | 39 | pub fn execute_matching_engine_upgrade( 40 | ctx: Context, 41 | ) -> Result<()> { 42 | utils::execute_upgrade(ctx.accounts, &ctx.bumps.execute_upgrade) 43 | } 44 | 45 | pub fn commit_matching_engine_upgrade(ctx: Context) -> Result<()> { 46 | processor::commit_matching_engine_upgrade(ctx) 47 | } 48 | 49 | // Token Router 50 | 51 | pub fn execute_token_router_upgrade(ctx: Context) -> Result<()> { 52 | utils::execute_upgrade(ctx.accounts, &ctx.bumps.execute_upgrade) 53 | } 54 | 55 | pub fn commit_token_router_upgrade(ctx: Context) -> Result<()> { 56 | processor::commit_token_router_upgrade(ctx) 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod test { 62 | use super::*; 63 | 64 | #[test] 65 | fn upgrade_authority() { 66 | let (actual_addr, actual_bump_seed) = 67 | Pubkey::find_program_address(&[UPGRADE_AUTHORITY_SEED_PREFIX], &crate::id()); 68 | assert_eq!(actual_bump_seed, UPGRADE_AUTHORITY_BUMP); 69 | assert_eq!(actual_addr, common::UPGRADE_MANAGER_AUTHORITY); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/src/processor/matching_engine_upgrade/execute.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, utils::AuthorizeUpgrade}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct ExecuteMatchingEngineUpgrade<'info> { 6 | /// CHECK: Seeds must be \["emitter"\] (Matching Engine program). 7 | #[account(mut)] 8 | matching_engine_custodian: UncheckedAccount<'info>, 9 | 10 | #[account( 11 | constraint = { 12 | require_keys_eq!( 13 | execute_upgrade.program.key(), 14 | matching_engine::id(), 15 | ); 16 | 17 | true 18 | } 19 | )] 20 | execute_upgrade: ExecuteUpgrade<'info>, 21 | } 22 | 23 | impl<'info> AuthorizeUpgrade<'info> for ExecuteMatchingEngineUpgrade<'info> { 24 | fn execute_upgrade_composite_mut(&mut self) -> &mut ExecuteUpgrade<'info> { 25 | &mut self.execute_upgrade 26 | } 27 | 28 | fn authorize_upgrade(&self) -> Result<()> { 29 | let admin = &self.execute_upgrade.admin; 30 | let program = &self.execute_upgrade.program; 31 | let custodian = &self.matching_engine_custodian; 32 | 33 | matching_engine::cpi::submit_ownership_transfer_request(CpiContext::new( 34 | program.to_account_info(), 35 | matching_engine::cpi::accounts::SubmitOwnershipTransferRequest { 36 | admin: matching_engine::cpi::accounts::OwnerOnlyMut { 37 | owner: admin.owner.to_account_info(), 38 | custodian: custodian.to_account_info(), 39 | }, 40 | new_owner: admin.upgrade_authority.to_account_info(), 41 | }, 42 | ))?; 43 | 44 | matching_engine::cpi::confirm_ownership_transfer_request(CpiContext::new_with_signer( 45 | program.to_account_info(), 46 | matching_engine::cpi::accounts::ConfirmOwnershipTransferRequest { 47 | pending_owner: admin.upgrade_authority.to_account_info(), 48 | custodian: custodian.to_account_info(), 49 | }, 50 | &[crate::UPGRADE_AUTHORITY_SIGNER_SEEDS], 51 | )) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/src/processor/matching_engine_upgrade/mod.rs: -------------------------------------------------------------------------------- 1 | mod execute; 2 | pub use execute::*; 3 | 4 | mod commit; 5 | pub use commit::*; 6 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/src/processor/mod.rs: -------------------------------------------------------------------------------- 1 | mod matching_engine_upgrade; 2 | pub use matching_engine_upgrade::*; 3 | 4 | mod token_router_upgrade; 5 | pub use token_router_upgrade::*; 6 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/src/processor/token_router_upgrade/execute.rs: -------------------------------------------------------------------------------- 1 | use crate::{composite::*, utils::AuthorizeUpgrade}; 2 | use anchor_lang::prelude::*; 3 | 4 | #[derive(Accounts)] 5 | pub struct ExecuteTokenRouterUpgrade<'info> { 6 | /// CHECK: Seeds must be \["emitter"\] (Token Router program). 7 | #[account(mut)] 8 | token_router_custodian: UncheckedAccount<'info>, 9 | 10 | #[account( 11 | constraint = { 12 | require_keys_eq!( 13 | execute_upgrade.program.key(), 14 | token_router::id(), 15 | ); 16 | 17 | true 18 | } 19 | )] 20 | execute_upgrade: ExecuteUpgrade<'info>, 21 | } 22 | 23 | impl<'info> AuthorizeUpgrade<'info> for ExecuteTokenRouterUpgrade<'info> { 24 | fn execute_upgrade_composite_mut(&mut self) -> &mut ExecuteUpgrade<'info> { 25 | &mut self.execute_upgrade 26 | } 27 | 28 | fn authorize_upgrade(&self) -> Result<()> { 29 | let admin = &self.execute_upgrade.admin; 30 | let program = &self.execute_upgrade.program; 31 | let custodian = &self.token_router_custodian; 32 | 33 | token_router::cpi::submit_ownership_transfer_request(CpiContext::new( 34 | program.to_account_info(), 35 | token_router::cpi::accounts::SubmitOwnershipTransferRequest { 36 | admin: token_router::cpi::accounts::OwnerOnlyMut { 37 | owner: admin.owner.to_account_info(), 38 | custodian: custodian.to_account_info(), 39 | }, 40 | new_owner: admin.upgrade_authority.to_account_info(), 41 | }, 42 | ))?; 43 | 44 | token_router::cpi::confirm_ownership_transfer_request(CpiContext::new_with_signer( 45 | program.to_account_info(), 46 | token_router::cpi::accounts::ConfirmOwnershipTransferRequest { 47 | pending_owner: admin.upgrade_authority.to_account_info(), 48 | custodian: custodian.to_account_info(), 49 | }, 50 | &[crate::UPGRADE_AUTHORITY_SIGNER_SEEDS], 51 | )) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/src/processor/token_router_upgrade/mod.rs: -------------------------------------------------------------------------------- 1 | mod execute; 2 | pub use execute::*; 3 | 4 | mod commit; 5 | pub use commit::*; 6 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/src/state/mod.rs: -------------------------------------------------------------------------------- 1 | mod upgrade_receipt; 2 | pub use upgrade_receipt::*; 3 | -------------------------------------------------------------------------------- /solana/programs/upgrade-manager/src/state/upgrade_receipt.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | /// Current state of an upgrade. 4 | #[derive(Debug, AnchorSerialize, AnchorDeserialize, Clone, InitSpace, PartialEq, Eq, Copy)] 5 | pub enum UpgradeStatus { 6 | /// No status set. 7 | None, 8 | /// An upgrade has been executed, but not committed. 9 | Uncommitted { buffer: Pubkey, slot: u64 }, 10 | } 11 | 12 | impl std::fmt::Display for UpgradeStatus { 13 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 14 | match self { 15 | UpgradeStatus::None => write!(f, "None"), 16 | UpgradeStatus::Uncommitted { buffer, slot } => { 17 | write!(f, "Uncommitted {{ buffer: {}, slot: {} }}", buffer, slot) 18 | } 19 | } 20 | } 21 | } 22 | 23 | /// An account which reflects the status of an upgrade after one has been executed. This account 24 | /// will only exist when an upgrade status is uncommitted. 25 | /// 26 | /// NOTE: Please be careful with modifying the schema of this account. If you upgrade a program 27 | /// without committing, and follow it with an Upgrade Manager program upgrade with a new receipt 28 | /// serialization, you will have a bad time. 29 | #[account] 30 | #[derive(Debug, InitSpace)] 31 | pub struct UpgradeReceipt { 32 | pub bump: u8, 33 | pub program_data_bump: u8, 34 | 35 | pub owner: Pubkey, 36 | pub status: UpgradeStatus, 37 | } 38 | 39 | impl UpgradeReceipt { 40 | pub const SEED_PREFIX: &'static [u8] = b"receipt"; 41 | } 42 | -------------------------------------------------------------------------------- /solana/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.75" 3 | components = [ 4 | "clippy", 5 | "rustfmt", 6 | "rustc-dev" 7 | ] 8 | profile = "minimal" -------------------------------------------------------------------------------- /solana/sh/kill_test_validator.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -euo pipefail 4 | 5 | kill $(cat .validator_pid) 6 | rm .validator_pid -------------------------------------------------------------------------------- /solana/sh/run_anchor_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### I really hope this is just a temporary script. We cannot specify clone-upgradable-programs in 4 | ### Anchor.toml, so we need to clone the upgradeable programs manually. 5 | 6 | bash $(dirname $0)/run_test_validator.sh 16 7 | 8 | ### Start up wait. 9 | sleep 10 10 | 11 | ### Run the tests. 12 | anchor run test-local 13 | 14 | EXIT_CODE=$? 15 | 16 | ### Finally kill the validator. 17 | kill $(cat .validator_pid) 18 | rm .validator_pid 19 | 20 | exit $EXIT_CODE 21 | -------------------------------------------------------------------------------- /solana/sh/run_anchor_test_upgrade.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### I really hope this is just a temporary script. We cannot specify clone-upgradable-programs in 4 | ### Anchor.toml, so we need to clone the upgradeable programs manually. 5 | 6 | bash $(dirname $0)/run_test_validator.sh 32 7 | 8 | ### Start up wait. 9 | sleep 10 10 | 11 | ### Run the tests. 12 | anchor run test-upgrade-fork 13 | 14 | EXIT_CODE=$? 15 | 16 | ### Finally kill the validator. 17 | kill $(cat .validator_pid) 18 | rm .validator_pid 19 | 20 | exit $EXIT_CODE 21 | -------------------------------------------------------------------------------- /solana/ts/scripts/dedupeVaaExample.ts: -------------------------------------------------------------------------------- 1 | import { deserialize } from "@wormhole-foundation/sdk-definitions"; 2 | import { VaaSpy } from "../src/wormhole/spy"; 3 | 4 | async function main() { 5 | console.log("init spy"); 6 | 7 | const spy = new VaaSpy({ 8 | spyHost: "localhost:7073", 9 | vaaFilters: [ 10 | { 11 | chain: "Pythnet", 12 | // nativeAddress: "BwDNn2qvZc6drt8Q4zRE2HHys64ZyPXhWxt51ADtWuc1", 13 | nativeAddress: "G9LV2mp9ua1znRAfYwZz5cPiJMAbo1T6mbjdQsDZuMJg", 14 | }, 15 | ], 16 | enableCleanup: true, 17 | seenThresholdMs: 5_000, 18 | intervalMs: 250, 19 | maxToRemove: 5, 20 | }); 21 | 22 | spy.onObservation(({ raw, parsed, chain, nativeAddress }) => { 23 | const vaa = deserialize("Uint8Array", raw); 24 | console.log( 25 | "observed", 26 | vaa.emitterChain, 27 | chain, 28 | nativeAddress, 29 | vaa.emitterAddress.toNative(vaa.emitterChain).toString(), 30 | vaa.sequence, 31 | ); 32 | }); 33 | } 34 | 35 | // Do it. 36 | main(); 37 | -------------------------------------------------------------------------------- /solana/ts/scripts/setUpTestnetTokenRouter.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Connection, 3 | Keypair, 4 | PublicKey, 5 | Transaction, 6 | sendAndConfirmTransaction, 7 | } from "@solana/web3.js"; 8 | import "dotenv/config"; 9 | import { TokenRouterProgram } from "../src/tokenRouter"; 10 | 11 | const PROGRAM_ID = "tD8RmtdcV7bzBeuFgyrFc8wvayj988ChccEzRQzo6md"; 12 | const USDC_MINT = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); 13 | 14 | // Here we go. 15 | main(); 16 | 17 | // impl 18 | 19 | async function main() { 20 | const connection = new Connection("https://api.devnet.solana.com", "confirmed"); 21 | const tokenRouter = new TokenRouterProgram(connection, PROGRAM_ID, USDC_MINT); 22 | 23 | if (process.env.SOLANA_PRIVATE_KEY === undefined) { 24 | throw new Error("SOLANA_PRIVATE_KEY is undefined"); 25 | } 26 | const payer = Keypair.fromSecretKey(Buffer.from(process.env.SOLANA_PRIVATE_KEY, "hex")); 27 | 28 | // Set up program. 29 | await intialize(tokenRouter, payer); 30 | } 31 | 32 | async function intialize(tokenRouter: TokenRouterProgram, payer: Keypair) { 33 | const connection = tokenRouter.program.provider.connection; 34 | 35 | const custodian = tokenRouter.custodianAddress(); 36 | console.log("custodian", custodian.toString()); 37 | 38 | const exists = await connection.getAccountInfo(custodian).then((acct) => acct != null); 39 | if (exists) { 40 | console.log("already initialized"); 41 | return; 42 | } 43 | 44 | const ix = await tokenRouter.initializeIx({ 45 | owner: payer.publicKey, 46 | ownerAssistant: payer.publicKey, 47 | }); 48 | 49 | await sendAndConfirmTransaction(connection, new Transaction().add(ix), [payer]) 50 | .catch((err) => { 51 | console.log(err.logs); 52 | throw err; 53 | }) 54 | .then((txSig) => { 55 | console.log("intialize", txSig); 56 | }); 57 | } 58 | -------------------------------------------------------------------------------- /solana/ts/src/cctp/index.ts: -------------------------------------------------------------------------------- 1 | export { MessageTransmitterProgram } from "./messageTransmitter"; 2 | export { CctpMessage, CctpTokenBurnMessage } from "./messages"; 3 | export { TokenMessengerMinterProgram } from "./tokenMessengerMinter"; 4 | -------------------------------------------------------------------------------- /solana/ts/src/cctp/messageTransmitter/MessageSent.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export class MessageSent { 4 | rentPayer: PublicKey; 5 | message: Buffer; 6 | 7 | constructor(rentPayer: PublicKey, message: Buffer) { 8 | this.rentPayer = rentPayer; 9 | this.message = message; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /solana/ts/src/cctp/messageTransmitter/MessageTransmitterConfig.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | 4 | export class MessageTransmitterConfig { 5 | owner: PublicKey; 6 | pendingOwner: PublicKey; 7 | attesterManager: PublicKey; 8 | pauser: PublicKey; 9 | paused: boolean; 10 | localDomain: number; 11 | version: number; 12 | signatureThreshold: number; 13 | enabledAttesters: Array; 14 | maxMessageBodySize: BN; 15 | nextAvailableNonce: BN; 16 | 17 | constructor( 18 | owner: PublicKey, 19 | pendingOwner: PublicKey, 20 | attesterManager: PublicKey, 21 | pauser: PublicKey, 22 | paused: boolean, 23 | localDomain: number, 24 | version: number, 25 | signatureThreshold: number, 26 | enabledAttesters: Array, 27 | maxMessageBodySize: BN, 28 | nextAvailableNonce: BN, 29 | ) { 30 | this.owner = owner; 31 | this.pendingOwner = pendingOwner; 32 | this.attesterManager = attesterManager; 33 | this.pauser = pauser; 34 | this.paused = paused; 35 | this.localDomain = localDomain; 36 | this.version = version; 37 | this.signatureThreshold = signatureThreshold; 38 | this.enabledAttesters = enabledAttesters; 39 | this.maxMessageBodySize = maxMessageBodySize; 40 | this.nextAvailableNonce = nextAvailableNonce; 41 | } 42 | 43 | static address(programId: PublicKey) { 44 | return PublicKey.findProgramAddressSync([Buffer.from("message_transmitter")], programId)[0]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /solana/ts/src/cctp/messageTransmitter/UsedNonces.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export const MAX_NONCES = 6400n; 4 | 5 | export class UsedNonses { 6 | static address(programId: PublicKey, remoteDomain: number, nonce: bigint) { 7 | const firstNonce = ((nonce - 1n) / MAX_NONCES) * MAX_NONCES + 1n; 8 | return PublicKey.findProgramAddressSync( 9 | [ 10 | Buffer.from("used_nonces"), 11 | Buffer.from(remoteDomain.toString()), 12 | Buffer.from(firstNonce.toString()), 13 | ], 14 | programId, 15 | )[0]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /solana/ts/src/cctp/tokenMessengerMinter/RemoteTokenMessenger.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export class RemoteTokenMessenger { 4 | domain: number; 5 | tokenMessenger: Array; 6 | 7 | constructor(domain: number, tokenMessenger: Array) { 8 | this.domain = domain; 9 | this.tokenMessenger = tokenMessenger; 10 | } 11 | 12 | static address(programId: PublicKey, remoteDomain: number) { 13 | return PublicKey.findProgramAddressSync( 14 | [Buffer.from("remote_token_messenger"), Buffer.from(remoteDomain.toString())], 15 | programId, 16 | )[0]; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /solana/ts/src/common/messages/deposit.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CctpDeposit, 3 | Message, 4 | messages, 5 | payloads, 6 | } from "@wormhole-foundation/example-liquidity-layer-definitions"; 7 | 8 | export const ID_DEPOSIT = messages("CctpDeposit").id; 9 | 10 | export const ID_DEPOSIT_FILL = payloads("Fill").id; 11 | export const ID_DEPOSIT_SLOW_ORDER_RESPONSE = payloads("SlowOrderResponse").id; 12 | 13 | export class LiquidityLayerDeposit { 14 | message: CctpDeposit; 15 | constructor(message: CctpDeposit) { 16 | this.message = message; 17 | } 18 | static decode(buf: Buffer): LiquidityLayerDeposit { 19 | return new LiquidityLayerDeposit(Message.deserialize(new Uint8Array(buf)) as CctpDeposit); 20 | } 21 | encode(): Buffer { 22 | return Buffer.from(Message.serialize(this.message)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /solana/ts/src/common/messages/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FastMarketOrder, 3 | Message, 4 | messages, 5 | } from "@wormhole-foundation/example-liquidity-layer-definitions"; 6 | import { isChain } from "@wormhole-foundation/sdk-base"; 7 | import { ID_DEPOSIT, LiquidityLayerDeposit } from "./deposit"; 8 | 9 | export * from "./deposit"; 10 | 11 | export const ID_FAST_MARKET_ORDER = messages("FastMarketOrder").id; 12 | 13 | export class LiquidityLayerMessage { 14 | deposit?: LiquidityLayerDeposit; 15 | fastMarketOrder?: FastMarketOrder; 16 | 17 | constructor(message: { deposit?: LiquidityLayerDeposit; fastMarketOrder?: FastMarketOrder }) { 18 | const { deposit, fastMarketOrder } = message; 19 | this.deposit = deposit; 20 | this.fastMarketOrder = fastMarketOrder; 21 | } 22 | 23 | static decode(buf: Buffer): LiquidityLayerMessage { 24 | const payloadId = buf.readUInt8(0); 25 | 26 | let deposit: LiquidityLayerDeposit | undefined; 27 | let fastMarketOrder: FastMarketOrder | undefined; 28 | 29 | switch (payloadId) { 30 | case ID_DEPOSIT: { 31 | deposit = LiquidityLayerDeposit.decode(buf); 32 | break; 33 | } 34 | case ID_FAST_MARKET_ORDER: { 35 | fastMarketOrder = Message.deserialize(new Uint8Array(buf)) as FastMarketOrder; 36 | if (!isChain(fastMarketOrder.targetChain)) throw new Error("Invalid target chain"); 37 | break; 38 | } 39 | default: { 40 | throw new Error("Invalid Liquidity Layer message"); 41 | } 42 | } 43 | 44 | return new LiquidityLayerMessage({ deposit, fastMarketOrder }); 45 | } 46 | 47 | encode(): Buffer { 48 | const { deposit, fastMarketOrder } = this; 49 | 50 | if (deposit !== undefined) { 51 | return deposit.encode(); 52 | } else if (fastMarketOrder !== undefined) { 53 | return Buffer.from(Message.serialize(fastMarketOrder)); 54 | } 55 | 56 | throw new Error("Invalid Liquidity Layer message"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /solana/ts/src/common/state/index.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { Uint64, writeUint64BE } from ".."; 3 | 4 | export function emitterAddress(programId: PublicKey): PublicKey { 5 | return PublicKey.findProgramAddressSync([Buffer.from("emitter")], programId)[0]; 6 | } 7 | 8 | export function coreMessageAddress(programId: PublicKey, associatedAccount: PublicKey): PublicKey { 9 | return messageAddress(programId, associatedAccount, "core-msg"); 10 | } 11 | 12 | export function cctpMessageAddress(programId: PublicKey, associatedAccount: PublicKey): PublicKey { 13 | return messageAddress(programId, associatedAccount, "cctp-msg"); 14 | } 15 | 16 | function messageAddress( 17 | programId: PublicKey, 18 | associatedAccount: PublicKey, 19 | prefix: string, 20 | ): PublicKey { 21 | return PublicKey.findProgramAddressSync( 22 | [Buffer.from(prefix), associatedAccount.toBuffer()], 23 | programId, 24 | )[0]; 25 | } 26 | 27 | export function coreMessageAddressOld( 28 | programId: PublicKey, 29 | payer: PublicKey, 30 | payerSequenceValue: Uint64, 31 | ): PublicKey { 32 | return messageAddressOld(programId, payer, payerSequenceValue, "core-msg"); 33 | } 34 | 35 | export function cctpMessageAddressOld( 36 | programId: PublicKey, 37 | payer: PublicKey, 38 | payerSequenceValue: Uint64, 39 | ): PublicKey { 40 | return messageAddressOld(programId, payer, payerSequenceValue, "cctp-msg"); 41 | } 42 | 43 | function messageAddressOld( 44 | programId: PublicKey, 45 | payer: PublicKey, 46 | payerSequenceValue: Uint64, 47 | prefix: string, 48 | ): PublicKey { 49 | const encodedPayerSequenceValue = Buffer.alloc(8); 50 | writeUint64BE(encodedPayerSequenceValue, payerSequenceValue); 51 | return PublicKey.findProgramAddressSync( 52 | [Buffer.from(prefix), payer.toBuffer(), encodedPayerSequenceValue], 53 | programId, 54 | )[0]; 55 | } 56 | -------------------------------------------------------------------------------- /solana/ts/src/idl/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ts/matching_engine"; 2 | export * from "./ts/token_router"; 3 | export * from "./ts/upgrade_manager"; 4 | 5 | import * as _MatchingEngineIdl from "./json/matching_engine.json"; 6 | import * as _TokenRouterIdl from "./json/token_router.json"; 7 | import * as _UpgradeManagerIdl from "./json/upgrade_manager.json"; 8 | const idl = { 9 | matchingEngine: _MatchingEngineIdl, 10 | tokenRouter: _TokenRouterIdl, 11 | upgradeManager: _UpgradeManagerIdl, 12 | }; 13 | export { idl }; 14 | -------------------------------------------------------------------------------- /solana/ts/src/index.ts: -------------------------------------------------------------------------------- 1 | import { ConfirmOptions } from "@solana/web3.js"; 2 | 3 | import { 4 | AddressLookupTableAccount, 5 | PublicKey, 6 | Signer, 7 | TransactionInstruction, 8 | } from "@solana/web3.js"; 9 | 10 | export type PreparedTransaction = { 11 | ixs: TransactionInstruction[]; 12 | signers: Signer[]; 13 | computeUnits: number; 14 | feeMicroLamports: number; 15 | nonceAccount?: PublicKey; 16 | addressLookupTableAccounts?: AddressLookupTableAccount[]; 17 | txName?: string; 18 | confirmOptions?: ConfirmOptions; 19 | }; 20 | 21 | export type PreparedTransactionOptions = { 22 | feeMicroLamports: number; 23 | computeUnits: number; 24 | nonceAccount?: PublicKey; 25 | addressLookupTableAccounts?: AddressLookupTableAccount[]; 26 | }; 27 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/Auction.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { MessageProtocol } from "./RouterEndpoint"; 4 | 5 | export type AuctionStatus = { 6 | notStarted?: {}; 7 | active?: {}; 8 | completed?: { slot: BN; executePenalty: BN | null }; 9 | settled?: { 10 | fee: BN; 11 | totalPenalty: BN | null; 12 | }; 13 | }; 14 | 15 | export type AuctionDestinationAssetInfo = { 16 | custodyTokenBump: number; 17 | amountOut: BN; 18 | }; 19 | 20 | export type AuctionInfo = { 21 | configId: number; 22 | custodyTokenBump: number; 23 | vaaSequence: BN; 24 | sourceChain: number; 25 | bestOfferToken: PublicKey; 26 | initialOfferToken: PublicKey; 27 | startSlot: BN; 28 | amountIn: BN; 29 | securityDeposit: BN; 30 | offerPrice: BN; 31 | redeemerMessageLen: number; 32 | destinationAssetInfo: AuctionDestinationAssetInfo | null; 33 | }; 34 | 35 | export class Auction { 36 | bump: number; 37 | vaaHash: number[]; 38 | vaaTimestamp: number; 39 | targetProtocol: MessageProtocol; 40 | status: AuctionStatus; 41 | preparedBy: PublicKey; 42 | info: AuctionInfo | null; 43 | 44 | constructor( 45 | bump: number, 46 | vaaHash: number[], 47 | vaaTimestamp: number, 48 | targetProtocol: MessageProtocol, 49 | status: AuctionStatus, 50 | preparedBy: PublicKey, 51 | info: AuctionInfo | null, 52 | ) { 53 | this.bump = bump; 54 | this.vaaHash = vaaHash; 55 | this.vaaTimestamp = vaaTimestamp; 56 | this.targetProtocol = targetProtocol; 57 | this.status = status; 58 | this.preparedBy = preparedBy; 59 | this.info = info; 60 | } 61 | 62 | static address(programId: PublicKey, vaaHash: Array | Buffer | Uint8Array) { 63 | return PublicKey.findProgramAddressSync( 64 | [Buffer.from("auction"), Buffer.from(vaaHash)], 65 | programId, 66 | )[0]; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/AuctionConfig.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | 4 | export type AuctionParameters = { 5 | userPenaltyRewardBps: number; 6 | initialPenaltyBps: number; 7 | duration: number; 8 | gracePeriod: number; 9 | penaltyPeriod: number; 10 | minOfferDeltaBps: number; 11 | securityDepositBase: BN; 12 | securityDepositBps: number; 13 | }; 14 | 15 | export class AuctionConfig { 16 | id: number; 17 | parameters: AuctionParameters; 18 | 19 | constructor(id: number, parameters: AuctionParameters) { 20 | this.id = id; 21 | this.parameters = parameters; 22 | } 23 | 24 | static address(programId: PublicKey, id: number) { 25 | const encodedId = Buffer.alloc(4); 26 | encodedId.writeUInt32BE(id); 27 | return PublicKey.findProgramAddressSync( 28 | [Buffer.from("auction-config"), encodedId], 29 | programId, 30 | )[0]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/AuctionHistory.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { AuctionInfo } from "./Auction"; 4 | import { Uint64, writeUint64BE } from "../../common"; 5 | 6 | export type AuctionHistoryHeader = { 7 | id: BN; 8 | minTimestamp: number | null; 9 | maxTimestamp: number | null; 10 | }; 11 | 12 | export type AuctionEntry = { 13 | vaaHash: Array; 14 | vaaTimestamp: number; 15 | info: AuctionInfo; 16 | }; 17 | 18 | export class AuctionHistory { 19 | header: AuctionHistoryHeader; 20 | data: Array; 21 | 22 | constructor(header: AuctionHistoryHeader, data: Array) { 23 | this.header = header; 24 | this.data = data; 25 | } 26 | 27 | static address(programId: PublicKey, id: Uint64) { 28 | const encodedId = Buffer.alloc(8); 29 | writeUint64BE(encodedId, id); 30 | return PublicKey.findProgramAddressSync( 31 | [Buffer.from("auction-history"), encodedId], 32 | programId, 33 | )[0]; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/Custodian.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { emitterAddress } from "../../common"; 4 | 5 | export class Custodian { 6 | owner: PublicKey; 7 | pendingOwner: PublicKey | null; 8 | paused: boolean; 9 | pausedSetBy: PublicKey | null; 10 | ownerAssistant: PublicKey; 11 | feeRecipientToken: PublicKey; 12 | auctionConfigId: number; 13 | nextProposalId: BN; 14 | 15 | constructor( 16 | owner: PublicKey, 17 | pendingOwner: PublicKey | null, 18 | paused: boolean, 19 | pausedSetBy: PublicKey | null, 20 | ownerAssistant: PublicKey, 21 | feeRecipientToken: PublicKey, 22 | auctionConfigId: number, 23 | nextProposalId: BN, 24 | ) { 25 | this.owner = owner; 26 | this.pendingOwner = pendingOwner; 27 | this.paused = paused; 28 | this.pausedSetBy = pausedSetBy; 29 | this.ownerAssistant = ownerAssistant; 30 | this.feeRecipientToken = feeRecipientToken; 31 | this.auctionConfigId = auctionConfigId; 32 | this.nextProposalId = nextProposalId; 33 | } 34 | 35 | static address(programId: PublicKey) { 36 | return emitterAddress(programId); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/FastFill.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { Uint64, writeUint64BE } from "../../common"; 4 | import { ChainId } from "@wormhole-foundation/sdk-base"; 5 | 6 | export type FastFillInfo = { 7 | preparedBy: PublicKey; 8 | amount: BN; 9 | redeemer: PublicKey; 10 | timestamp: BN; 11 | }; 12 | 13 | export type FastFillSeeds = { 14 | sourceChain: number; 15 | orderSender: Array; 16 | sequence: BN; 17 | bump: number; 18 | }; 19 | 20 | export class FastFill { 21 | seeds: FastFillSeeds; 22 | redeemed: boolean; 23 | info: FastFillInfo; 24 | redeemerMessage: Buffer; 25 | 26 | constructor( 27 | seeds: FastFillSeeds, 28 | redeemed: boolean, 29 | info: FastFillInfo, 30 | redeemerMessage: Buffer, 31 | ) { 32 | this.seeds = seeds; 33 | this.redeemed = redeemed; 34 | this.info = info; 35 | this.redeemerMessage = redeemerMessage; 36 | } 37 | 38 | static address( 39 | programId: PublicKey, 40 | sourceChain: ChainId, 41 | orderSender: Array, 42 | sequence: Uint64, 43 | ) { 44 | const encodedSourceChain = Buffer.alloc(2); 45 | encodedSourceChain.writeUInt16BE(sourceChain); 46 | 47 | const encodedSequence = Buffer.alloc(8); 48 | writeUint64BE(encodedSequence, sequence); 49 | 50 | return PublicKey.findProgramAddressSync( 51 | [ 52 | Buffer.from("fast-fill"), 53 | encodedSourceChain, 54 | Buffer.from(orderSender), 55 | encodedSequence, 56 | ], 57 | programId, 58 | )[0]; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/FastFillSequencer.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { ChainId } from "@wormhole-foundation/sdk-base"; 4 | 5 | export type FastFillSequencerSeeds = { 6 | sourceChain: number; 7 | sender: Array; 8 | bump: number; 9 | }; 10 | 11 | export class FastFillSequencer { 12 | seeds: FastFillSequencerSeeds; 13 | nextSequence: BN; 14 | 15 | constructor(seeds: FastFillSequencerSeeds, nextSequence: BN) { 16 | this.seeds = seeds; 17 | this.nextSequence = nextSequence; 18 | } 19 | 20 | static address(programId: PublicKey, sourceChain: ChainId, sender: Array) { 21 | const encodedSourceChain = Buffer.alloc(2); 22 | encodedSourceChain.writeUInt16BE(sourceChain); 23 | 24 | return PublicKey.findProgramAddressSync( 25 | [Buffer.from("fast-fill-sequencer"), encodedSourceChain, Buffer.from(sender)], 26 | programId, 27 | )[0]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/PreparedOrderResponse.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { VaaHash } from "../../common"; 4 | import { EndpointInfo } from "./RouterEndpoint"; 5 | 6 | export type PreparedOrderResponseSeeds = { 7 | fastVaaHash: Array; 8 | bump: number; 9 | }; 10 | 11 | export type PreparedOrderResponseInfo = { 12 | preparedBy: PublicKey; 13 | baseFeeToken: PublicKey; 14 | fastVaaTimestamp: number; 15 | sourceChain: number; 16 | baseFee: BN; 17 | initAuctionFee: BN; 18 | sender: Array; 19 | redeemer: Array; 20 | amountIn: BN; 21 | }; 22 | 23 | export class PreparedOrderResponse { 24 | seeds: PreparedOrderResponseSeeds; 25 | info: PreparedOrderResponseInfo; 26 | toEndpoint: EndpointInfo; 27 | redeemerMessage: Buffer; 28 | 29 | constructor( 30 | seeds: PreparedOrderResponseSeeds, 31 | info: PreparedOrderResponseInfo, 32 | toEndpoint: EndpointInfo, 33 | redeemerMessage: Buffer, 34 | ) { 35 | this.seeds = seeds; 36 | this.info = info; 37 | this.toEndpoint = toEndpoint; 38 | this.redeemerMessage = redeemerMessage; 39 | } 40 | 41 | static address(programId: PublicKey, fastVaaHash: VaaHash) { 42 | return PublicKey.findProgramAddressSync( 43 | [Buffer.from("order-response"), Buffer.from(fastVaaHash)], 44 | programId, 45 | )[0]; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/Proposal.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { AuctionParameters } from "./AuctionConfig"; 4 | import { Uint64, uint64ToBN, writeUint64BE } from "../../common"; 5 | 6 | export type ProposalAction = { 7 | none?: {}; 8 | updateAuctionParameters?: { 9 | id: number; 10 | parameters: AuctionParameters; 11 | }; 12 | }; 13 | 14 | export class Proposal { 15 | id: BN; 16 | bump: number; 17 | action: ProposalAction; 18 | by: PublicKey; 19 | owner: PublicKey; 20 | slotProposedAt: BN; 21 | slotEnactDelay: BN; 22 | slotEnactedAt: BN | null; 23 | constructor( 24 | id: BN, 25 | bump: number, 26 | action: ProposalAction, 27 | by: PublicKey, 28 | owner: PublicKey, 29 | slotProposedAt: Uint64, 30 | slotEnactDelay: Uint64, 31 | slotEnactedAt: Uint64 | null, 32 | ) { 33 | this.id = id; 34 | this.bump = bump; 35 | this.action = action; 36 | this.by = by; 37 | this.owner = owner; 38 | this.slotProposedAt = uint64ToBN(slotProposedAt); 39 | this.slotEnactDelay = uint64ToBN(slotEnactDelay); 40 | this.slotEnactedAt = slotEnactedAt === null ? null : uint64ToBN(slotEnactedAt); 41 | } 42 | 43 | static address(programId: PublicKey, nextProposalId: Uint64) { 44 | const encodedProposalId = Buffer.alloc(8); 45 | writeUint64BE(encodedProposalId, nextProposalId); 46 | 47 | return PublicKey.findProgramAddressSync( 48 | [Buffer.from("proposal"), encodedProposalId], 49 | programId, 50 | )[0]; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/ReservedFastFillSequence.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { VaaHash } from "../../common"; 3 | import { FastFillSeeds } from "./FastFill"; 4 | 5 | export type ReservedFastFillSequenceSeeds = { 6 | fastVaaHash: Array; 7 | bump: number; 8 | }; 9 | 10 | export class ReservedFastFillSequence { 11 | seeds: ReservedFastFillSequenceSeeds; 12 | beneficiary: PublicKey; 13 | fastFillSeeds: FastFillSeeds; 14 | 15 | constructor( 16 | seeds: ReservedFastFillSequenceSeeds, 17 | beneficiary: PublicKey, 18 | fastFillSeeds: FastFillSeeds, 19 | ) { 20 | this.seeds = seeds; 21 | this.beneficiary = beneficiary; 22 | this.fastFillSeeds = fastFillSeeds; 23 | } 24 | 25 | static address(programId: PublicKey, fastVaaHash: VaaHash) { 26 | return PublicKey.findProgramAddressSync( 27 | [Buffer.from("reserved-fast-fill-sequence"), Buffer.from(fastVaaHash)], 28 | programId, 29 | )[0]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/RouterEndpoint.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export type MessageProtocol = { 4 | local?: { programId: PublicKey }; 5 | cctp?: { domain: number }; 6 | none?: {}; 7 | }; 8 | 9 | export type EndpointInfo = { 10 | chain: number; 11 | address: Array; 12 | mintRecipient: Array; 13 | protocol: MessageProtocol; 14 | }; 15 | 16 | export class RouterEndpoint { 17 | bump: number; 18 | info: EndpointInfo; 19 | 20 | constructor(bump: number, info: EndpointInfo) { 21 | this.bump = bump; 22 | this.info = info; 23 | } 24 | 25 | static address(programId: PublicKey, chain: number) { 26 | const encodedChain = Buffer.alloc(2); 27 | encodedChain.writeUInt16BE(chain); 28 | return PublicKey.findProgramAddressSync( 29 | [Buffer.from("endpoint"), encodedChain], 30 | programId, 31 | )[0]; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /solana/ts/src/matchingEngine/state/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Auction"; 2 | export * from "./AuctionConfig"; 3 | export * from "./AuctionHistory"; 4 | export * from "./Custodian"; 5 | export * from "./FastFill"; 6 | export * from "./FastFillSequencer"; 7 | export * from "./PreparedOrderResponse"; 8 | export * from "./Proposal"; 9 | export * from "./ReservedFastFillSequence"; 10 | export * from "./RouterEndpoint"; 11 | -------------------------------------------------------------------------------- /solana/ts/src/testing/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./consts"; 2 | export * from "./mock"; 3 | export * from "./utils"; 4 | -------------------------------------------------------------------------------- /solana/ts/src/tokenRouter/state/Custodian.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | import { emitterAddress } from "../../common"; 3 | 4 | export class Custodian { 5 | paused: boolean; 6 | owner: PublicKey; 7 | pendingOwner: PublicKey | null; 8 | ownerAssistant: PublicKey; 9 | pausedSetBy: PublicKey; 10 | 11 | constructor( 12 | paused: boolean, 13 | owner: PublicKey, 14 | pendingOwner: PublicKey | null, 15 | ownerAssistant: PublicKey, 16 | pausedSetBy: PublicKey, 17 | ) { 18 | this.paused = paused; 19 | this.owner = owner; 20 | this.pendingOwner = pendingOwner; 21 | this.ownerAssistant = ownerAssistant; 22 | this.pausedSetBy = pausedSetBy; 23 | } 24 | 25 | static address(programId: PublicKey) { 26 | return emitterAddress(programId); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /solana/ts/src/tokenRouter/state/PreparedFill.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | 4 | export type FillType = { 5 | unset?: {}; 6 | wormholeCctpDeposit?: {}; 7 | fastFill?: {}; 8 | }; 9 | 10 | export type PreparedFillSeeds = { 11 | fillSource: PublicKey; 12 | bump: number; 13 | }; 14 | 15 | export type PreparedFillInfo = { 16 | preparedCustodyTokenBump: number; 17 | preparedBy: PublicKey; 18 | fillType: FillType; 19 | sourceChain: number; 20 | orderSender: Array; 21 | redeemer: PublicKey; 22 | timestamp: BN; 23 | }; 24 | 25 | export class PreparedFill { 26 | seeds: PreparedFillSeeds; 27 | info: PreparedFillInfo; 28 | redeemerMessage: Buffer; 29 | 30 | constructor(seeds: PreparedFillSeeds, info: PreparedFillInfo, redeemerMessage: Buffer) { 31 | this.seeds = seeds; 32 | this.info = info; 33 | this.redeemerMessage = redeemerMessage; 34 | } 35 | 36 | static address(programId: PublicKey, fillSource: PublicKey) { 37 | return PublicKey.findProgramAddressSync( 38 | [Buffer.from("fill"), fillSource.toBuffer()], 39 | programId, 40 | )[0]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /solana/ts/src/tokenRouter/state/PreparedOrder.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | import { Keccak } from "sha3"; 4 | import { Uint64, uint64ToBN } from "../../common"; 5 | 6 | export type OrderType = { 7 | market?: { 8 | minAmountOut: BN | null; 9 | }; 10 | }; 11 | 12 | export type PreparedOrderInfo = { 13 | preparedCustodyTokenBump: number; 14 | orderSender: PublicKey; 15 | preparedBy: PublicKey; 16 | orderType: OrderType; 17 | srcToken: PublicKey; 18 | refundToken: PublicKey; 19 | targetChain: number; 20 | redeemer: Array; 21 | }; 22 | 23 | export class PreparedOrder { 24 | info: PreparedOrderInfo; 25 | redeemerMessage: Buffer; 26 | 27 | constructor(info: PreparedOrderInfo, redeemerMessage: Buffer) { 28 | this.info = info; 29 | this.redeemerMessage = redeemerMessage; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /solana/ts/src/tokenRouter/state/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Custodian"; 2 | export * from "./PreparedFill"; 3 | export * from "./PreparedOrder"; 4 | 5 | import { utils } from "@wormhole-foundation/sdk-solana"; 6 | import { BN } from "@coral-xyz/anchor"; 7 | import { PublicKey } from "@solana/web3.js"; 8 | 9 | export function deriveCoreMessageKey(programId: PublicKey, payer: PublicKey, sequence: BN) { 10 | return utils.deriveAddress( 11 | [Buffer.from("msg"), payer.toBuffer(), sequence.toBuffer()], 12 | programId, 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /solana/ts/src/upgradeManager/state/UpgradeReceipt.ts: -------------------------------------------------------------------------------- 1 | import { BN } from "@coral-xyz/anchor"; 2 | import { PublicKey } from "@solana/web3.js"; 3 | 4 | export type UpgradeStatus = { 5 | none?: {}; 6 | uncommitted?: { 7 | buffer: PublicKey; 8 | slot: BN; 9 | }; 10 | }; 11 | 12 | export class UpgradeReceipt { 13 | bump: number; 14 | programDataBump: number; 15 | owner: PublicKey; 16 | status: UpgradeStatus; 17 | 18 | constructor(bump: number, programDataBump: number, owner: PublicKey, status: UpgradeStatus) { 19 | this.bump = bump; 20 | this.programDataBump = programDataBump; 21 | this.owner = owner; 22 | this.status = status; 23 | } 24 | 25 | static address(programId: PublicKey, otherProgram: PublicKey) { 26 | return PublicKey.findProgramAddressSync( 27 | [Buffer.from("receipt"), otherProgram.toBuffer()], 28 | programId, 29 | )[0]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /solana/ts/src/upgradeManager/state/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./UpgradeReceipt"; 2 | -------------------------------------------------------------------------------- /solana/ts/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { PublicKey } from "@solana/web3.js"; 2 | 3 | export const BPF_LOADER_UPGRADEABLE_PROGRAM_ID = new PublicKey( 4 | "BPFLoaderUpgradeab1e11111111111111111111111", 5 | ); 6 | 7 | export function programDataAddress(programId: PublicKey) { 8 | return PublicKey.findProgramAddressSync( 9 | [programId.toBuffer()], 10 | BPF_LOADER_UPGRADEABLE_PROGRAM_ID, 11 | )[0]; 12 | } 13 | 14 | export class ArrayQueue { 15 | private _data: Array; 16 | private _size: number = 0; 17 | private _index: number = 0; 18 | 19 | constructor(capacity?: number) { 20 | this._data = new Array(capacity ?? 256).fill(null); 21 | } 22 | 23 | head(): T | null { 24 | if (this.isEmpty()) { 25 | return null; 26 | } 27 | 28 | return this._data[this._index]!; 29 | } 30 | 31 | enqueue(value: T): void { 32 | const data = this._data; 33 | const size = this._size; 34 | const index = this._index; 35 | 36 | if (size + 1 > data.length) { 37 | this.resize(); 38 | } 39 | 40 | data[(index + size) % data.length] = value; 41 | ++this._size; 42 | } 43 | 44 | dequeue(): void { 45 | if (this.isEmpty()) { 46 | return; 47 | } 48 | 49 | const data = this._data; 50 | const index = this._index; 51 | 52 | this._index = (index + 1) % data.length; 53 | --this._size; 54 | } 55 | 56 | resize(): void { 57 | const data = this._data; 58 | const size = this._size; 59 | const index = this._index; 60 | 61 | const newData = new Array(size * 2); 62 | for (let i = 0; i < size; ++i) { 63 | newData[i] = data[(index + i) % data.length]; 64 | } 65 | 66 | this._data = newData; 67 | this._index = 0; 68 | } 69 | 70 | isEmpty(): boolean { 71 | return this._size == 0; 72 | } 73 | 74 | length(): number { 75 | return this._size; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /solana/ts/tests/accounts/core_bridge/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "2yVjuQwpsvdsrywzsJJVs9Ueh4zayyo5DYJbBNc3DDpn", 3 | "account": { 4 | "lamports": 1057920, 5 | "data": ["AAAAAMbrG4wAAAAAgFEBAAoAAAAAAAAA", "base64"], 6 | "owner": "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth", 7 | "executable": false, 8 | "rentEpoch": 18446744073709551615, 9 | "space": 24 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /solana/ts/tests/accounts/core_bridge/fee_collector.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "9bFNrXNb2WTx8fMHXCheaZqkLZ3YCCaiqTftHxeintHy", 3 | "account": { 4 | "lamports": 2350640070, 5 | "data": ["", "base64"], 6 | "owner": "11111111111111111111111111111111", 7 | "executable": false, 8 | "rentEpoch": 18446744073709551615, 9 | "space": 0 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /solana/ts/tests/accounts/core_bridge/guardian_set_0.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "DS7qfSAgYsonPpKoAjcGhX9VFjXdGkiHjEDkTidf8H2P", 3 | "account": { 4 | "lamports": 21141440, 5 | "data": ["AAAAAAEAAAC++kKdV80Yt/ik2RotqatK8F0PvkPJm2EAAAAA", "base64"], 6 | "owner": "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth", 7 | "executable": false, 8 | "rentEpoch": 18446744073709551615, 9 | "space": 36 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /solana/ts/tests/accounts/message_transmitter/message_transmitter_config.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "BWrwSWjbikT3H7qHAkUEbLmwDQoB4ZDJ4wcSEhSPTZCu", 3 | "account": { 4 | "lamports": 2519520, 5 | "data": [ 6 | "Ryi0jhPLI/wfOQgPIIpMNon4r0rVMO7Sy1fUtUxQmdUxE51OObvhOoDFz5C0iApooK3hQfndo8m3eRHbcLcd6T35aIm/9s3sgMXPkLSICmigreFB+d2jybd5Edtwtx3pPfloib/2zeyAxc+QtIgKaKCt4UH53aPJt3kR23C3Hek9+WiJv/bN7AAFAAAAAAAAAAEAAAABAAAAAAAAAAAAAAAAAAAAvvpCnVfNGLf4pNkaLamrSvBdD77QBwAAAAAAACYAAAAAAAAA/w==", 7 | "base64" 8 | ], 9 | "owner": "CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 234 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /solana/ts/tests/accounts/testnet/matching_engine_custodian.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "5BsCKkzuZXLygduw6RorCqEB61AdzNkxp5VzQrFGzYWr", 3 | "account": { 4 | "lamports": 1927920, 5 | "data": [ 6 | "hOSLuHDkbPAMGliG/hCT35/EOMKW+fcnW3cYtrwOFW2NM2xY8IOZbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACyYhuGDPpdfzJOBnceTvHX8S6d3fMAJtKCp45SBiW1cXT3n2+4rU8d7ccb0Cv2HoY7eHIPeYyfdHVa3M3rheUeAgAAAAIAAAAAAAAAK/Yehjt4cg95jJ90dVrczeuF5R4BAAAAAQAAAAAAAAA=", 7 | "base64" 8 | ], 9 | "owner": "mPydpGUWxzERTNpyvTKdvS7v8kvw5sgwfiP8WQFrXVS", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 149 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /solana/ts/tests/accounts/testnet/token_router_custodian.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "CFYdtHYDnQgCAcwetWVjVg5V8Uiy1CpJaoYJxmV19Z7N", 3 | "account": { 4 | "lamports": 1851360, 5 | "data": [ 6 | "hOSLuHDkbPAADBpYhv4Qk9+fxDjClvn3J1t3GLa8DhVtjTNsWPCDmW0AsmIbhgz6XX8yTgZ3Hk7x1/Eund3zACbSgqeOUgYltXGyYhuGDPpdfzJOBnceTvHX8S6d3fMAJtKCp45SBiW1cQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "tD8RmtdcV7bzBeuFgyrFc8wvayj988ChccEzRQzo6md", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 138 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /solana/ts/tests/accounts/token_messenger_minter/arbitrum_remote_token_messenger.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "REzxi9nX3Eqseha5fBiaJhTC6SFJx4qJhP83U4UCrtc", 3 | "account": { 4 | "lamports": 1197120, 5 | "data": [ 6 | "aXOuIl/pivwDAAAAAAAAAAAAAAAAAAAAGTMNENnMh1Ehjq9R6IhdBYZC4Io=", 7 | "base64" 8 | ], 9 | "owner": "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 44 13 | } 14 | } -------------------------------------------------------------------------------- /solana/ts/tests/accounts/token_messenger_minter/ethereum_remote_token_messenger.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "Hazwi3jFQtLKc2ughi7HFXPkpDeso7DQaMR9Ks4afh3j", 3 | "account": { 4 | "lamports": 1197120, 5 | "data": [ 6 | "aXOuIl/pivwAAAAAAAAAAAAAAAAAAAAAvT+oG1i6kqghNgOLJa3scGavMVU=", 7 | "base64" 8 | ], 9 | "owner": "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 44 13 | } 14 | } -------------------------------------------------------------------------------- /solana/ts/tests/accounts/token_messenger_minter/misconfigured_remote_token_messenger.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "BWyFzH6LsnmDAaDWbGsriQ9SiiKq1CF6pbH4Ye3kzSBV", 3 | "account": { 4 | "lamports": 1197120, 5 | "data": [ 6 | "aXOuIl/pivwAAAAAAAAAAAAAAAAAAAAA0MPaWPVTWBQrjT4GwcMMXGEU7+g=", 7 | "base64" 8 | ], 9 | "owner": "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 44 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /solana/ts/tests/accounts/token_messenger_minter/token_messenger.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "Afgq3BHEfCE7d78D2XE9Bfyu2ieDqvE24xX8KDwreBms", 3 | "account": { 4 | "lamports": 1649520, 5 | "data": [ 6 | "ogTyNJPz3WCAxc+QtIgKaKCt4UH53aPJt3kR23C3Hek9+WiJv/bN7B85CA8gikw2ifivStUw7tLLV9S1TFCZ1TETnU45u+E6pl/JidtfXUJ1nzpUYFjvzc3AvzwYmActjrRd0dgFCM4AAAAA/Q==", 7 | "base64" 8 | ], 9 | "owner": "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 109 13 | } 14 | } -------------------------------------------------------------------------------- /solana/ts/tests/accounts/token_messenger_minter/token_minter.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "DBD8hAwLDRQkTsu6EqviaYNGKPnsAMmQonxf7AH8ZcFY", 3 | "account": { 4 | "lamports": 1405920, 5 | "data": [ 6 | "eoVUPzmfq86Axc+QtIgKaKCt4UH53aPJt3kR23C3Hek9+WiJv/bN7IDFz5C0iApooK3hQfndo8m3eRHbcLcd6T35aIm/9s3sAP0=", 7 | "base64" 8 | ], 9 | "owner": "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 74 13 | } 14 | } -------------------------------------------------------------------------------- /solana/ts/tests/accounts/token_messenger_minter/usdc_custody_token.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "FSxJ85FXVsXSr51SeWf9ciJWTcRnqKFSmBgRDeL3KyWw", 3 | "account": { 4 | "lamports": 2039280, 5 | "data": [ 6 | "xvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWG06cUFm0yAK1JhEHKELuHdBTqROz8nrg0RaRNBnQEQ50z48TPzLgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 165 13 | } 14 | } -------------------------------------------------------------------------------- /solana/ts/tests/accounts/token_messenger_minter/usdc_local_token.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "72bvEFk2Usi2uYc1SnaTNhBcQPc6tiJWXr9oKk7rkd4C", 3 | "account": { 4 | "lamports": 1795680, 5 | "data": [ 6 | "n4M6qsFUgLbWqajhGAScPd+PLCmG/7q4c+v9bJKeja8GCPmRo4QduMb6evO+2606PWXzaqvJdDGxu+TC0vbg5HymAgNFL11hABCl1OgAAACbOAAAAAAAAP8oAAAAAAAAcuTt4iFhAAAAAAAAAAAAAKClHaPafQAAAAAAAAAAAAD9/w==", 7 | "base64" 8 | ], 9 | "owner": "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 130 13 | } 14 | } -------------------------------------------------------------------------------- /solana/ts/tests/accounts/token_messenger_minter/usdc_token_pair.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "8d1jdvvMFhJfxSzPXcDGtifcGMTvUxc2EpWFstbNzcTL", 3 | "account": { 4 | "lamports": 1426800, 5 | "data": [ 6 | "EdYtsOWVxUcAAAAAAAAAAAAAAAAAAAAAoLhpkcYhizbB0Z1KLp6wzjYG60hZjy8Y6Y4jOy9QMTMaodmv8oz5ObYth/BStPTxPbusEf8=", 7 | "base64" 8 | ], 9 | "owner": "CCTPiPYPc6AsJuwueEnWgSgucamXDZwBd53dQ11YiKX3", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 77 13 | } 14 | } -------------------------------------------------------------------------------- /solana/ts/tests/accounts/usdc_mint.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", 3 | "account": { 4 | "lamports": 14801671630, 5 | "data": [ 6 | "AQAAAAwaWIb+EJPfn8Q4wpb59ydbdxi2vA4VbY0zbFjwg5ltAICAaSIj9QAGAQEAAACoBjP/Bn2I36XUNXv0TibOzM8IZmiBA8a6YJ+kTBjSCA==", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 82 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /solana/ts/tests/accounts/usdc_payer_token.json: -------------------------------------------------------------------------------- 1 | { 2 | "pubkey": "4tKtuvtQ4TzkkrkESnRpbfSXCEZPkZe3eL5tCFUdpxtf", 3 | "account": { 4 | "lamports": 2039280, 5 | "data": [ 6 | "xvp6877brTo9ZfNqq8l0MbG75MLS9uDkfKYCA0UvXWEMGliG/hCT35/EOMKW+fcnW3cYtrwOFW2NM2xY8IOZbQC0AoadfgEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", 7 | "base64" 8 | ], 9 | "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", 10 | "executable": false, 11 | "rentEpoch": 18446744073709551615, 12 | "space": 165 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /solana/ts/tests/keys/pFCBP4bhqdSsrWUVTgqhPsLrfEdChBK17vgFM7TxjxQ.json: -------------------------------------------------------------------------------- 1 | [112,55,233,99,229,91,68,85,207,63,10,46,103,0,49,250,22,189,30,167,157,146,26,148,175,155,212,104,86,182,185,192,12,26,88,134,254,16,147,223,159,196,56,194,150,249,247,39,91,119,24,182,188,14,21,109,141,51,108,88,240,131,153,109] -------------------------------------------------------------------------------- /solana/tsconfig.anchor.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2020"], 6 | "module": "commonjs", 7 | "target": "es2020", 8 | "strict": true, 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true 11 | } 12 | } -------------------------------------------------------------------------------- /solana/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends":"./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "outDir": "dist/cjs" 7 | } 8 | } -------------------------------------------------------------------------------- /solana/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist/esm" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /solana/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "ts/src", 5 | "resolveJsonModule": true, 6 | "esModuleInterop": true, 7 | "module": "ESNext", 8 | "moduleResolution": "Bundler" 9 | }, 10 | "include": ["ts/src/**/*.ts", "ts/src/**/*.json"] 11 | } 12 | -------------------------------------------------------------------------------- /solver/.gitignore: -------------------------------------------------------------------------------- 1 | /cfg/**/*.json 2 | !/cfg/**/sample.*.json 3 | -------------------------------------------------------------------------------- /solver/Makefile: -------------------------------------------------------------------------------- 1 | SPY_NETWORK_mainnet=/wormhole/mainnet/2 2 | SPY_BOOTSTRAP_mainnet="/dns4/wormhole-v2-mainnet-bootstrap.xlabs.xyz/udp/8999/quic/p2p/12D3KooWNQ9tVrcb64tw6bNs2CaNrUGPM7yRrKvBBheQ5yCyPHKC,/dns4/wormhole.mcf.rocks/udp/8999/quic/p2p/12D3KooWDZVv7BhZ8yFLkarNdaSWaB43D6UbQwExJ8nnGAEmfHcU,/dns4/wormhole-v2-mainnet-bootstrap.staking.fund/udp/8999/quic/p2p/12D3KooWG8obDX9DNi1KUwZNu9xkGwfKqTp2GFwuuHpWZ3nQruS1" 3 | 4 | SPY_NETWORK_testnet=/wormhole/testnet/2/1 5 | SPY_BOOTSTRAP_testnet="/dns4/t-guardian-01.testnet.xlabs.xyz/udp/8999/quic/p2p/12D3KooWCW3LGUtkCVkHZmVSZHzL3C4WRKWfqAiJPz1NR7dT9Bxh,/dns4/t-guardian-02.testnet.xlabs.xyz/udp/8999/quic/p2p/12D3KooWJXA6goBCiWM8ucjzc4jVUBSqL9Rri6UpjHbkMPErz5zK" 6 | 7 | .PHONY: wormhole-spy-up 8 | wormhole-spy-up: 9 | ifdef SPY_NETWORK_$(NETWORK) 10 | WORMHOLE_NETWORK_ID=$(SPY_NETWORK_$(NETWORK)) \ 11 | WORMHOLE_BOOTSTRAP=$(SPY_BOOTSTRAP_$(NETWORK)) \ 12 | NATS_STREAM="${NETWORK}-vaas" \ 13 | docker-compose -f docker-compose.yml up -d 14 | endif 15 | 16 | .PHONY: wormhole-spy-down 17 | wormhole-spy-down: 18 | docker-compose -f docker-compose.yml down 19 | 20 | .PHONY: wormhole-spy-restart 21 | wormhole-spy-restart: wormhole-spy-down wormhole-spy-up 22 | -------------------------------------------------------------------------------- /solver/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.7" 2 | services: 3 | nats: 4 | container_name: solana-nats-1 5 | image: nats:latest 6 | command: --jetstream 7 | ports: 8 | - "4222:4222" 9 | 10 | beacon: 11 | container_name: solana-beacon-1 12 | image: public.ecr.aws/pyth-network/beacon:v1.1.3 13 | platform: linux/amd64 14 | environment: 15 | WORMHOLE_NETWORK_ID: ${WORMHOLE_NETWORK_ID} 16 | WORMHOLE_BOOTSTRAP: ${WORMHOLE_BOOTSTRAP} 17 | WORMHOLE_LISTEN_PORT: "8999" 18 | NATS_URL: "solana-nats-1:4222" 19 | NATS_STREAM: ${NATS_STREAM} 20 | SERVER_URL: "0.0.0.0:7073" 21 | LOG_LEVEL: "info" 22 | WRITER_BATCH_SIZE: "1000" 23 | ports: 24 | - "7073:7073" 25 | # required to let the container to increase UDP buffer size 26 | # without giving it full root priviledges 27 | cap_add: 28 | - "NET_ADMIN" 29 | -------------------------------------------------------------------------------- /solver/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wormhole-foundation/example-liquidity-layer-solver", 3 | "version": "0.4.0", 4 | "author": "Wormhole Contributors", 5 | "license": "Apache-2.0", 6 | "main": "./dist/cjs/index.js", 7 | "types": "./dist/cjs/index.d.ts", 8 | "module": "./dist/esm/index.js", 9 | "scripts": { 10 | "execute": "tsx src/executeOrder/app.ts", 11 | "improve": "tsx src/improveOffer/app.ts", 12 | "relayer": "tsx src/vaaAuctionRelayer/app.ts", 13 | "clean": "rm -rf node_modules" 14 | }, 15 | "dependencies": { 16 | "@solana/spl-token": "^0.4.6", 17 | "@wormhole-foundation/example-liquidity-layer-evm": "0.4.0", 18 | "@wormhole-foundation/example-liquidity-layer-solana": "0.4.0", 19 | "@wormhole-foundation/sdk-base": "^1.4.4", 20 | "@wormhole-foundation/sdk-definitions": "^1.4.4", 21 | "@wormhole-foundation/sdk-solana": "^1.4.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /solver/src/containers/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CachedBlockhash"; 2 | export * from "./OfferToken"; 3 | -------------------------------------------------------------------------------- /solver/src/utils/evm/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./wormholeCctp"; 2 | -------------------------------------------------------------------------------- /solver/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./config"; 2 | export * as evm from "./evm"; 3 | export * from "./logger"; 4 | export * from "./wormscan"; 5 | export * from "./settleAuction"; 6 | export * from "./sendTx"; 7 | export * from "./preparePostVaaTx"; 8 | export * from "./placeInitialOffer"; 9 | 10 | import { Connection, PublicKey } from "@solana/web3.js"; 11 | import * as splToken from "@solana/spl-token"; 12 | import { LiquidityLayerMessage } from "@wormhole-foundation/example-liquidity-layer-solana/common"; 13 | import { 14 | FastMarketOrder, 15 | SlowOrderResponse, 16 | payloads, 17 | } from "@wormhole-foundation/example-liquidity-layer-definitions"; 18 | 19 | const USDC_MINT = new PublicKey("4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU"); 20 | 21 | export async function getUsdcAtaBalance(connection: Connection, owner: PublicKey) { 22 | const { amount } = await splToken.getAccount( 23 | connection, 24 | splToken.getAssociatedTokenAddressSync(USDC_MINT, owner), 25 | ); 26 | return amount; 27 | } 28 | 29 | export async function isBalanceSufficient( 30 | connection: Connection, 31 | owner: PublicKey, 32 | amount: bigint, 33 | ) { 34 | return (await getUsdcAtaBalance(connection, owner)) >= amount; 35 | } 36 | 37 | export function tryParseFastMarketOrder(payload: Buffer): FastMarketOrder | undefined { 38 | try { 39 | let { fastMarketOrder } = LiquidityLayerMessage.decode(payload); 40 | if (fastMarketOrder === undefined) { 41 | return undefined; 42 | } else { 43 | return fastMarketOrder; 44 | } 45 | } catch (err: any) { 46 | return undefined; 47 | } 48 | } 49 | 50 | export function tryParseSlowOrderResponse(payload: Buffer): SlowOrderResponse | undefined { 51 | try { 52 | const { deposit } = LiquidityLayerMessage.decode(payload); 53 | if ( 54 | deposit === undefined || 55 | deposit.message.payload.id !== payloads("SlowOrderResponse").id 56 | ) { 57 | return undefined; 58 | } else { 59 | return deposit.message.payload; 60 | } 61 | } catch (err: any) { 62 | return undefined; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /solver/src/utils/logger.ts: -------------------------------------------------------------------------------- 1 | import * as winston from "winston"; 2 | 3 | export function defaultLogger(args: { label: string; level: string }): winston.Logger { 4 | const { label, level } = args; 5 | 6 | return winston.createLogger({ 7 | transports: [ 8 | new winston.transports.Console({ 9 | level, 10 | }), 11 | ], 12 | format: winston.format.combine( 13 | winston.format.colorize(), 14 | winston.format.label({ label }), 15 | winston.format.splat(), 16 | winston.format.timestamp({ 17 | format: "YYYY-MM-DD HH:mm:ss.SSS", 18 | }), 19 | winston.format.errors({ stack: true }), 20 | winston.format.printf(({ level, message, label, timestamp }) => { 21 | return `${timestamp} [${label}] ${level}: ${message}`; 22 | }) 23 | ), 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /solver/src/utils/wormscan.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import * as winston from "winston"; 3 | import { AppConfig } from "./config"; 4 | 5 | interface VaaId { 6 | chain: number; 7 | sequence: number; 8 | emitter: string; 9 | } 10 | 11 | interface VaaResponse { 12 | vaa?: Buffer; 13 | txHash?: string; 14 | } 15 | 16 | export async function fetchVaaFromWormscan( 17 | cfg: AppConfig, 18 | vaaId: VaaId, 19 | logger: winston.Logger, 20 | ): Promise { 21 | const wormscanRequest = `${cfg.wormholeScanVaaEndpoint()}${vaaId.chain}/${vaaId.emitter}/${ 22 | vaaId.sequence 23 | }`; 24 | 25 | const { maxRetries, retryBackoff } = cfg.sourceTxHash(); 26 | 27 | let vaaResponse: VaaResponse = {}; 28 | let retries = 0; 29 | while ( 30 | vaaResponse.txHash == undefined && 31 | vaaResponse.vaa == undefined && 32 | retries < maxRetries 33 | ) { 34 | const backoff = retries * retryBackoff; 35 | logger.debug( 36 | `Requesting VaaResponse from Wormscan, retries=${retries}, maxRetries=${maxRetries}`, 37 | ); 38 | 39 | await new Promise((resolve) => setTimeout(resolve, backoff)); 40 | 41 | const response = await fetch(wormscanRequest); 42 | const parsedResponse = await response.json(); 43 | vaaResponse.txHash = "0x" + parsedResponse.data.txHash; 44 | vaaResponse.vaa = Buffer.from(parsedResponse.data.vaa, "base64"); 45 | 46 | ++retries; 47 | } 48 | 49 | return vaaResponse; 50 | } 51 | -------------------------------------------------------------------------------- /solver/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends":"../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "rootDir": "./src" 7 | }, 8 | "include": ["src"] 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "NodeNext", 5 | "moduleResolution": "NodeNext", 6 | "types": ["mocha", "chai", "node"], 7 | "composite": true, 8 | "esModuleInterop": true, 9 | "strict": true, 10 | "incremental": true, 11 | "declaration": true, 12 | "declarationMap": true, 13 | "sourceMap": true, 14 | "skipLibCheck": true, 15 | "noErrorTruncation": true, 16 | // Strict Checks 17 | // "alwaysStrict": true, 18 | // "noImplicitAny": true, 19 | // "strictNullChecks": true, 20 | // "strictPropertyInitialization": true, 21 | // "useUnknownInCatchVariables": true, 22 | // "strictFunctionTypes": true, 23 | // "noImplicitThis": true, 24 | // "strictBindCallApply": true, 25 | // "noPropertyAccessFromIndexSignature": true, 26 | // "noUncheckedIndexedAccess": true, 27 | // //"exactOptionalPropertyTypes": false, 28 | // // Linter Checks 29 | // "noImplicitReturns": false, 30 | // "noImplicitOverride": true, 31 | // "forceConsistentCasingInFileNames": true, 32 | // // https://eslint.org/docs/rules/consistent-return ? 33 | // "noFallthroughCasesInSwitch": true, 34 | // // https://eslint.org/docs/rules/no-fallthrough 35 | // "noUnusedLocals": true, 36 | // // https://eslint.org/docs/rules/no-unused-vars 37 | // "noUnusedParameters": false, 38 | // // https://eslint.org/docs/rules/no-unused-vars#args 39 | // "allowUnreachableCode": false, 40 | // // https://eslint.org/docs/rules/no-unreachable ? 41 | // "allowUnusedLabels": false, 42 | // // https://eslint.org/docs/rules/no-unused-labels 43 | // // Base Strict Checks 44 | // "noImplicitUseStrict": false, 45 | // "suppressExcessPropertyErrors": false, 46 | // "suppressImplicitAnyIndexErrors": false, 47 | // "noStrictGenericChecks": false, 48 | } 49 | } -------------------------------------------------------------------------------- /universal/rs/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock -------------------------------------------------------------------------------- /universal/rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "messages" 4 | ] 5 | resolver = "2" 6 | 7 | [workspace.package] 8 | edition = "2021" 9 | version = "0.0.0" 10 | authors = ["Wormhole Contributors"] 11 | license = "Apache-2.0" 12 | homepage = "https://wormhole.com" 13 | repository = "https://github.com/wormhole-foundation/example-liquidity-layer" 14 | 15 | [workspace.dependencies] 16 | wormhole-io = "0.3.0-alpha.0" 17 | wormhole-raw-vaas = "0.3.0-alpha.0" 18 | hex-literal = "0.4.1" 19 | 20 | [workspace.lints.clippy] 21 | correctness = { level = "warn", priority = -1 } 22 | 23 | arithmetic_side_effects = "deny" 24 | as_conversions = "deny" 25 | cast_abs_to_unsigned = "deny" 26 | cast_lossless= "deny" 27 | cast_possible_truncation = "deny" 28 | cast_possible_wrap = "deny" 29 | cast_precision_loss = "deny" 30 | cast_sign_loss = "deny" 31 | eq_op = "deny" 32 | expect_used = "deny" 33 | float_cmp = "deny" 34 | integer_division = "deny" 35 | large_futures = "deny" 36 | large_stack_arrays = "deny" 37 | large_stack_frames = "deny" 38 | lossy_float_literal = "deny" 39 | manual_slice_size_calculation = "deny" 40 | modulo_one = "deny" 41 | out_of_bounds_indexing = "deny" 42 | overflow_check_conditional = "deny" 43 | panic = "deny" 44 | recursive_format_impl = "deny" 45 | todo = "deny" 46 | unchecked_duration_subtraction = "deny" 47 | unreachable = "deny" 48 | -------------------------------------------------------------------------------- /universal/rs/messages/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "liquidity-layer-messages" 3 | edition.workspace = true 4 | version.workspace = true 5 | authors.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | wormhole-io.workspace = true 14 | wormhole-raw-vaas.workspace = true 15 | 16 | [dev-dependencies] 17 | hex-literal.workspace = true 18 | 19 | [lints] 20 | workspace = true 21 | -------------------------------------------------------------------------------- /universal/rs/messages/src/deposit/mod.rs: -------------------------------------------------------------------------------- 1 | mod fill; 2 | pub use fill::*; 3 | 4 | mod slow_order_response; 5 | pub use slow_order_response::*; 6 | -------------------------------------------------------------------------------- /universal/rs/messages/src/deposit/slow_order_response.rs: -------------------------------------------------------------------------------- 1 | //! Slow Order Response 2 | 3 | use wormhole_io::{Readable, TypePrefixedPayload, Writeable}; 4 | 5 | #[derive(Debug, Clone, PartialEq, Eq)] 6 | pub struct SlowOrderResponse { 7 | pub base_fee: u64, 8 | } 9 | 10 | impl Readable for SlowOrderResponse { 11 | fn read(reader: &mut R) -> std::io::Result 12 | where 13 | Self: Sized, 14 | R: std::io::Read, 15 | { 16 | Ok(Self { 17 | base_fee: Readable::read(reader)?, 18 | }) 19 | } 20 | } 21 | 22 | impl Writeable for SlowOrderResponse { 23 | fn write(&self, writer: &mut W) -> std::io::Result<()> 24 | where 25 | Self: Sized, 26 | W: std::io::Write, 27 | { 28 | self.base_fee.write(writer) 29 | } 30 | } 31 | 32 | impl TypePrefixedPayload<1> for SlowOrderResponse { 33 | const TYPE: Option<[u8; 1]> = Some([2]); 34 | 35 | fn written_size(&self) -> usize { 36 | 8 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod test { 42 | use crate::raw; 43 | 44 | use super::*; 45 | 46 | #[test] 47 | fn serde() { 48 | let slow_order_response = SlowOrderResponse { 49 | base_fee: 1234567890, 50 | }; 51 | 52 | let encoded = slow_order_response.to_vec(); 53 | 54 | let message = raw::LiquidityLayerDepositMessage::parse(&encoded).unwrap(); 55 | let parsed = message.to_slow_order_response_unchecked(); 56 | 57 | let expected = SlowOrderResponse { 58 | base_fee: parsed.base_fee(), 59 | }; 60 | 61 | assert_eq!(slow_order_response, expected); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /universal/rs/messages/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod deposit; 2 | pub use deposit::*; 3 | 4 | mod fast_market_order; 5 | pub use fast_market_order::*; 6 | 7 | pub mod raw; 8 | 9 | pub use wormhole_io; 10 | -------------------------------------------------------------------------------- /universal/rs/rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.75" 3 | components = [ 4 | "clippy", 5 | "rustfmt", 6 | "rustc-dev" 7 | ] 8 | profile = "minimal" -------------------------------------------------------------------------------- /universal/ts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@wormhole-foundation/example-liquidity-layer-definitions", 3 | "version": "0.4.0", 4 | "author": "Wormhole Contributors", 5 | "license": "Apache-2.0", 6 | "main": "./dist/cjs/index.js", 7 | "types": "./dist/cjs/index.d.ts", 8 | "module": "./dist/esm/index.js", 9 | "files": [ 10 | "./dist/cjs", 11 | "./dist/esm" 12 | ], 13 | "scripts": { 14 | "build:esm": "tsc -p tsconfig.esm.json", 15 | "build:cjs": "tsc -p tsconfig.cjs.json", 16 | "build": "npm run build:esm && npm run build:cjs", 17 | "test:layouts": "npx ts-mocha -p ./tsconfig.json tests/layouts.ts", 18 | "test": "npm run test:layouts", 19 | "clean": "rm -rf node_modules && rm -rf dist" 20 | }, 21 | "dependencies": { 22 | "@wormhole-foundation/sdk-base": "^1.4.4", 23 | "@wormhole-foundation/sdk-definitions": "^1.4.4" 24 | }, 25 | "devDependencies": { 26 | "@types/chai": "^4.3.4", 27 | "@types/mocha": "^10.0.1", 28 | "@types/node": "^18.14.5", 29 | "chai": "^4.3.7", 30 | "dotenv": "^16.3.1", 31 | "envfile": "^6.18.0", 32 | "mocha": "^10.0.0", 33 | "prettier": "^2.8.7", 34 | "prettier-plugin-solidity": "^1.1.3", 35 | "ts-mocha": "^10.0.0", 36 | "typechain": "^8.1.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /universal/ts/tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "moduleResolution": "Node", 6 | "outDir": "./dist/cjs" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /universal/ts/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/esm" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /universal/ts/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends":"../../tsconfig.json", 3 | "compilerOptions": { 4 | "rootDir": "./src" 5 | }, 6 | "include": ["src"] 7 | } 8 | --------------------------------------------------------------------------------