// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {Test} from "forge-std/Test.sol"; import {BridgeEscrowVault} from "../../../contracts/bridge/interop/BridgeEscrowVault.sol"; import {BridgeRegistry} from "../../../contracts/bridge/interop/BridgeRegistry.sol"; import {wXRP} from "../../../contracts/bridge/interop/wXRP.sol"; import {MintBurnController} from "../../../contracts/bridge/interop/MintBurnController.sol"; import {BridgeVerifier} from "../../../contracts/bridge/interop/BridgeVerifier.sol"; contract BridgeIntegrationTest is Test { BridgeEscrowVault public vault; BridgeRegistry public registry; wXRP public wxrp; MintBurnController public controller; BridgeVerifier public verifier; address public admin = address(0x1); address public operator = address(0x2); address public user = address(0x5); address public hsmSigner = address(0x4); uint256 public attestor1Pk = 0x10; uint256 public attestor2Pk = 0x11; uint256 public attestor3Pk = 0x12; address public attestor1; address public attestor2; address public attestor3; function setUp() public { attestor1 = vm.addr(attestor1Pk); attestor2 = vm.addr(attestor2Pk); attestor3 = vm.addr(attestor3Pk); vm.startPrank(admin); // Deploy registry registry = new BridgeRegistry(admin); registry.grantRole(registry.REGISTRAR_ROLE(), admin); // Register Polygon destination registry.registerDestination(137, "Polygon", 128, 3600, 10, address(0x200)); // Deploy vault vault = new BridgeEscrowVault(admin); vault.grantRole(vault.OPERATOR_ROLE(), operator); // Deploy wXRP wxrp = new wXRP(admin); // Deploy controller controller = new MintBurnController(admin, address(wxrp), hsmSigner); wxrp.grantRole(wxrp.MINTER_ROLE(), address(controller)); wxrp.grantRole(wxrp.BURNER_ROLE(), address(controller)); // Deploy verifier verifier = new BridgeVerifier(admin, 6667); // 66.67% quorum verifier.addAttestor(attestor1, 1000); verifier.addAttestor(attestor2, 1000); verifier.addAttestor(attestor3, 1000); vm.stopPrank(); vm.deal(user, 100 ether); } function test_FullEVMBridgeFlow() public { // 1. User deposits native ETH vm.startPrank(user); bytes32 transferId = vault.depositNative{value: 1 ether}( BridgeEscrowVault.DestinationType.EVM, abi.encodePacked(address(0x100)), 3600, keccak256("test-transfer") ); vm.stopPrank(); // 2. Operator confirms deposit vm.startPrank(operator); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.DEPOSIT_CONFIRMED); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.ROUTE_SELECTED); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.EXECUTING); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.DESTINATION_SENT); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.FINALITY_CONFIRMED); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.COMPLETED); vm.stopPrank(); // Verify final state BridgeEscrowVault.Transfer memory transfer = vault.getTransfer(transferId); assertEq(uint8(transfer.status), uint8(BridgeEscrowVault.TransferStatus.COMPLETED)); } function test_XRPLBridgeFlow() public { // 1. User deposits for XRPL bridge vm.startPrank(user); bytes32 transferId = vault.depositNative{value: 1 ether}( BridgeEscrowVault.DestinationType.XRPL, abi.encodePacked(address(0x200)), 3600, keccak256("xrpl-transfer") ); vm.stopPrank(); // 2. Operator processes XRPL transfer vm.startPrank(operator); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.DEPOSIT_CONFIRMED); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.ROUTE_SELECTED); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.EXECUTING); // In production, XRPL payment would be executed here vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.DESTINATION_SENT); vault.updateTransferStatus(transferId, BridgeEscrowVault.TransferStatus.COMPLETED); vm.stopPrank(); BridgeEscrowVault.Transfer memory transfer = vault.getTransfer(transferId); assertEq(uint8(transfer.status), uint8(BridgeEscrowVault.TransferStatus.COMPLETED)); } function test_wXRP_MintBurn() public { bytes32 xrplTxHash = keccak256("xrpl-lock-tx"); // Mint wXRP vm.startPrank(admin); // In production, this would be called by controller with HSM signature wxrp.mint(user, 1000 * 10**18, xrplTxHash); vm.stopPrank(); assertEq(wxrp.balanceOf(user), 1000 * 10**18); // Burn wXRP vm.startPrank(admin); wxrp.burnFrom(user, 500 * 10**18, keccak256("xrpl-unlock-tx")); vm.stopPrank(); assertEq(wxrp.balanceOf(user), 500 * 10**18); } function test_AttestationQuorum() public { bytes32 transferId = keccak256("test-attestation"); bytes32 proofHash = keccak256("proof-data"); uint256 deadline = block.timestamp + 3600; // Attestor 1 submits attestation vm.startPrank(attestor1); bytes memory sig1 = _signAttestation(attestor1Pk, transferId, proofHash, 1, deadline); verifier.submitAttestation( BridgeVerifier.Attestation({ transferId: transferId, proofHash: proofHash, nonce: 1, deadline: deadline, signature: sig1 }) ); vm.stopPrank(); // Attestor 2 submits attestation vm.startPrank(attestor2); bytes memory sig2 = _signAttestation(attestor2Pk, transferId, proofHash, 2, deadline); verifier.submitAttestation( BridgeVerifier.Attestation({ transferId: transferId, proofHash: proofHash, nonce: 2, deadline: deadline, signature: sig2 }) ); vm.stopPrank(); // Check quorum (2/3 = 66.67% >= 66.67%) (bool quorumMet, uint256 totalWeight, uint256 requiredWeight) = verifier.verifyQuorum(transferId); assertTrue(quorumMet); assertGe(totalWeight, requiredWeight); } function _signAttestation( uint256 signerPk, bytes32 transferId, bytes32 proofHash, uint256 nonce, uint256 deadline ) internal view returns (bytes memory) { bytes32 typeHash = keccak256("Attestation(bytes32 transferId,bytes32 proofHash,uint256 nonce,uint256 deadline)"); bytes32 structHash = keccak256(abi.encode(typeHash, transferId, proofHash, nonce, deadline)); bytes32 domainSeparator = keccak256(abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes("BridgeVerifier")), keccak256(bytes("1")), block.chainid, address(verifier) )); bytes32 hash = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); (uint8 v, bytes32 r, bytes32 s) = vm.sign(signerPk, hash); return abi.encodePacked(r, s, v); } }