From 337f8cf49434bdea03fe1234938e1fc74a8fe0f7 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Tue, 4 Nov 2025 18:40:02 +0530 Subject: [PATCH 01/10] wip --- contracts/evmx/watcher/Watcher.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index 4630a74b..256d2ba4 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -56,6 +56,8 @@ contract Watcher is Initializable, Configurations { } function addPayloadData(RawPayload calldata rawPayload_, address appGateway_) external { + // todo: check and revert if already has a payload data + payloadData = rawPayload_; currentPayloadId = getCurrentPayloadId( payloadData.transaction.chainSlug, From ae5baa7ab45942807956b9245b18a9162027c36c Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 6 Nov 2025 22:49:12 +0530 Subject: [PATCH 02/10] fix: deadline check --- contracts/evmx/watcher/Watcher.sol | 6 ++++-- contracts/evmx/watcher/precompiles/ReadPrecompile.sol | 4 +++- contracts/evmx/watcher/precompiles/SchedulePrecompile.sol | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index 256d2ba4..11e8efc9 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -129,12 +129,15 @@ contract Watcher is Initializable, Configurations { ) internal { Payload storage p = _payloads[resolvedPromise_.payloadId]; if (p.isPayloadExecuted) return; + if (p.isPayloadCancelled) return; p.isPayloadExecuted = true; p.resolvedAt = block.timestamp; - _markResolved(resolvedPromise_); IPrecompile(precompiles[p.callType]).resolvePayload(p); + bool success = _markResolved(resolvedPromise_); + if (!success) return; + _settlePayload(resolvedPromise_.payloadId, feesUsed_); emit PayloadResolved(resolvedPromise_.payloadId); } @@ -143,7 +146,6 @@ contract Watcher is Initializable, Configurations { PromiseReturnData memory resolvedPromise_ ) internal returns (bool success) { Payload storage payloadParams = _payloads[resolvedPromise_.payloadId]; - if (payloadParams.deadline < block.timestamp) revert DeadlinePassed(); address asyncPromise = payloadParams.asyncPromise; if (asyncPromise != address(0)) { diff --git a/contracts/evmx/watcher/precompiles/ReadPrecompile.sol b/contracts/evmx/watcher/precompiles/ReadPrecompile.sol index fa317f6e..4dd757ad 100644 --- a/contracts/evmx/watcher/precompiles/ReadPrecompile.sol +++ b/contracts/evmx/watcher/precompiles/ReadPrecompile.sol @@ -64,7 +64,9 @@ contract ReadPrecompile is IPrecompile { ); } - function resolvePayload(Payload calldata payload) external onlyWatcher {} + function resolvePayload(Payload calldata payload) external onlyWatcher { + if (block.timestamp > payloadParams_.deadline) revert DeadlinePassed(); + } function setFees(uint256 readFees_) external onlyWatcher { readFees = readFees_; diff --git a/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol b/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol index f36282db..2e82b20a 100644 --- a/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol @@ -142,8 +142,8 @@ contract SchedulePrecompile is IPrecompile { function resolvePayload(Payload calldata payloadParams_) external onlyWatcher { (, uint256 executeAfter) = abi.decode(payloadParams_.precompileData, (uint256, uint256)); - if (executeAfter > block.timestamp) revert ResolvingScheduleTooEarly(); + if (block.timestamp > payloadParams_.deadline) revert DeadlinePassed(); emit ScheduleResolved(payloadParams_.payloadId); } From 35ee2b7ab8d8550f724f7ec98be85a8eb34a3aec Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 7 Nov 2025 00:48:08 +0530 Subject: [PATCH 03/10] feat: solana libs --- contracts/evmx/base/AppGatewayBase.sol | 3 - contracts/evmx/fees/Credit.sol | 161 +++- contracts/evmx/fees/FeesManager.sol | 14 +- contracts/evmx/helpers/ForwarderSolana.sol | 102 ++ .../evmx/helpers/solana-utils/Ed25519.sol | 906 ++++++++++++++++++ .../evmx/helpers/solana-utils/Ed25519_pow.sol | 329 +++++++ .../evmx/helpers/solana-utils/Sha512.sol | 278 ++++++ .../evmx/helpers/solana-utils/SolanaPda.sol | 270 ++++++ .../helpers/solana-utils/SolanaSignature.sol | 17 + .../solana-utils/program-pda/FeesPlugPdas.sol | 47 + .../evmx/watcher/borsh-serde/BorshDecoder.sol | 359 +++++++ .../evmx/watcher/borsh-serde/BorshEncoder.sol | 317 ++++++ .../evmx/watcher/borsh-serde/BorshUtils.sol | 132 +++ .../watcher/precompiles/ReadPrecompile.sol | 2 +- .../precompiles/SchedulePrecompile.sol | 2 + .../watcher/precompiles/WritePrecompile.sol | 118 ++- .../protocol/switchboard/SwitchboardBase.sol | 1 + contracts/utils/common/Constants.sol | 5 - contracts/utils/common/Structs.sol | 89 +- foundry.toml | 9 +- test/apps/counter/CounterAppGateway.sol | 2 +- 21 files changed, 3102 insertions(+), 61 deletions(-) create mode 100644 contracts/evmx/helpers/ForwarderSolana.sol create mode 100644 contracts/evmx/helpers/solana-utils/Ed25519.sol create mode 100644 contracts/evmx/helpers/solana-utils/Ed25519_pow.sol create mode 100644 contracts/evmx/helpers/solana-utils/Sha512.sol create mode 100644 contracts/evmx/helpers/solana-utils/SolanaPda.sol create mode 100644 contracts/evmx/helpers/solana-utils/SolanaSignature.sol create mode 100644 contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol create mode 100644 contracts/evmx/watcher/borsh-serde/BorshDecoder.sol create mode 100644 contracts/evmx/watcher/borsh-serde/BorshEncoder.sol create mode 100644 contracts/evmx/watcher/borsh-serde/BorshUtils.sol diff --git a/contracts/evmx/base/AppGatewayBase.sol b/contracts/evmx/base/AppGatewayBase.sol index 442eab75..9b54b506 100644 --- a/contracts/evmx/base/AppGatewayBase.sol +++ b/contracts/evmx/base/AppGatewayBase.sol @@ -26,9 +26,6 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { // slot 54 OverrideParams public overrideParams; - // slot 55 - bytes public onCompleteData; - // slot 57 mapping(address => bool) public isValidPromise; diff --git a/contracts/evmx/fees/Credit.sol b/contracts/evmx/fees/Credit.sol index eb12043e..93f825ff 100644 --- a/contracts/evmx/fees/Credit.sol +++ b/contracts/evmx/fees/Credit.sol @@ -13,10 +13,15 @@ import "../interfaces/IFeesPool.sol"; import {AddressResolverUtil} from "../helpers/AddressResolverUtil.sol"; import {NonceUsed, InvalidAmount, InsufficientCreditsAvailable, InsufficientBalance, InvalidChainSlug, NotRequestHandler, InvalidReceiver} from "../../utils/common/Errors.sol"; -import {WRITE, FAST} from "../../utils/common/Constants.sol"; +import {WRITE, CHAIN_SLUG_SOLANA_MAINNET} from "../../utils/common/Constants.sol"; import "../../utils/RescueFundsLib.sol"; import "../base/AppGatewayBase.sol"; import {toBytes32Format} from "../../utils/common/Converters.sol"; +import {ForwarderSolana} from "../helpers/ForwarderSolana.sol"; +import {SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../utils/common/Structs.sol"; +import {FeesPlugProgramPda} from "../helpers/solana-utils/program-pda/FeesPlugPdas.sol"; +import {SolanaPDA} from "../helpers/solana-utils/SolanaPda.sol"; +import {TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID} from "../helpers/solana-utils/SolanaPda.sol"; abstract contract FeesManagerStorage is IFeesManager { // slots [0-49] reserved for gap @@ -40,7 +45,7 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 53 // token pool balances // chainSlug => token address => amount - mapping(uint32 => mapping(address => uint256)) public tokenOnChainBalances; + mapping(uint32 => mapping(bytes32 => uint256)) public tokenOnChainBalances; // slot 54 /// @notice Mapping to track nonce to whether it has been used @@ -58,10 +63,15 @@ abstract contract FeesManagerStorage is IFeesManager { /// @dev chainSlug => max fees mapping(uint32 => uint256) public maxFeesPerChainSlug; - // slots [57-106] reserved for gap - uint256[50] _gap_after; + ForwarderSolana public forwarderSolana; - // slots [107-156] 50 slots reserved for address resolver util + bytes32 public susdcSolanaProgramId; + bytes32 public feesPlugSolanaProgramId; + + // slots [60-107] reserved for gap + uint256[44] _gap_after; + + // slots [108-157] 50 slots reserved for address resolver util // 9 slots for app gateway base } @@ -99,6 +109,9 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew /// @notice Emitted when withdraw fails event WithdrawFailed(bytes32 indexed payloadId); + /// @notice Emitted when fees plug solana program id is set + event FeesPlugSolanaSet(bytes32 indexed feesPlugSolanaProgramId); + function setFeesPlug(uint32 chainSlug_, bytes32 feesPlug_) external onlyOwner { feesPlugs[chainSlug_] = feesPlug_; emit FeesPlugSet(chainSlug_, feesPlug_); @@ -133,7 +146,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew uint256 nativeAmount_, uint256 creditAmount_ ) external override onlyWatcher { - tokenOnChainBalances[chainSlug_][token_] += creditAmount_ + nativeAmount_; + tokenOnChainBalances[chainSlug_][toBytes32Format(token_)] += creditAmount_ + nativeAmount_; // Mint tokens to the user _mint(depositTo_, creditAmount_); @@ -250,7 +263,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew // Burn tokens from sender _burn(consumeFrom, credits_); - tokenOnChainBalances[chainSlug_][token_] -= credits_; + tokenOnChainBalances[chainSlug_][toBytes32Format(token_)] -= credits_; // Add it to the queue and submit request _createRequest( @@ -261,6 +274,44 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew ); } + function withdrawCreditsSolana( + uint32 chainSlug_, + bytes32 token_, + uint256 credits_, + uint256 maxFees_, + bytes32 onchainReceiver_ + ) public async { + // sender is evmx address (credit holder) that is making the call (it is will be AG in case of Game) + address consumeFrom = msg.sender; + + // Check if amount is available in fees plug + uint256 availableCredits = balanceOf(consumeFrom); + if (availableCredits < credits_ + maxFees_) revert InsufficientCreditsAvailable(); + + // Burn tokens from sender + _burn(consumeFrom, credits_); + tokenOnChainBalances[chainSlug_][token_] -= credits_; + + feesPlugWithdrawSolana(token_, credits_, onchainReceiver_); + } + + function feesPlugWithdrawSolana( + bytes32 token_, + uint256 credits_, + bytes32 onchainReceiver_ + ) internal { + SolanaInstruction memory solanaInstruction_ = createFeesPlugWithdrawInstructionSolana( + onchainReceiver_, + token_, + credits_ + ); + forwarderSolana.callSolana( + abi.encode(solanaInstruction_), + solanaInstruction_.data.programId, + address(this) + ); + } + function _createRequest( uint32 chainSlug_, address consumeFrom_, @@ -311,4 +362,100 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew function decimals() public pure override returns (uint8) { return 18; } + + function createFeesPlugWithdrawInstructionSolana( + bytes32 userAddress_, + bytes32 susdcMint_, + uint256 withdrawAmount_ + ) internal view returns (SolanaInstruction memory) { + bytes32[] memory accounts = new bytes32[](11); + // accounts 0 - tmpReturnStoragePda + (accounts[0], ) = FeesPlugProgramPda.deriveTmpReturnStoragePda(feesPlugSolanaProgramId); + /*----------------- mint() accounts -----------------*/ + // accounts 1 - programConfigPda + (accounts[1], ) = FeesPlugProgramPda.deriveProgramConfigPda(feesPlugSolanaProgramId); + // accounts 2 - vaultConfigPda + (bytes32 vaultConfigPda, ) = FeesPlugProgramPda.deriveVaultConfigPda( + feesPlugSolanaProgramId + ); + accounts[2] = vaultConfigPda; + // accounts 3 - token mint address + accounts[3] = susdcMint_; + // accounts 4 - whitelistedTokenPda + (accounts[4], ) = FeesPlugProgramPda.deriveWhitelistedTokenPda( + feesPlugSolanaProgramId, + susdcMint_ + ); + // accounts 5 - receiver address + accounts[5] = userAddress_; + // accounts 6 - receiver ata address + accounts[6] = SolanaPDA.deriveTokenAtaAddress(userAddress_, susdcMint_); + // accounts 7 - vault ata address + accounts[7] = SolanaPDA.deriveTokenAtaAddress(vaultConfigPda, susdcMint_); + // accounts 8 - system program id + accounts[8] = SYSTEM_PROGRAM_ID; + // accounts 9 - token program id + accounts[9] = TOKEN_PROGRAM_ID; + // accounts 10 - associated token program id + accounts[10] = ASSOCIATED_TOKEN_PROGRAM_ID; + + bytes1[] memory accountFlags = new bytes1[](11); + // tmpReturnStoragePda is writable + accountFlags[0] = bytes1(0x01); // true + // programConfigPda is not writable + accountFlags[1] = bytes1(0x00); // false + // vaultConfigPda is writable + accountFlags[2] = bytes1(0x01); // true + // token mint address is not writable + accountFlags[3] = bytes1(0x00); // false + // whitelistedTokenPda is not writable + accountFlags[4] = bytes1(0x00); // false + // receiver address is writable + accountFlags[5] = bytes1(0x01); // true + // receiver ata address is writable + accountFlags[6] = bytes1(0x01); // true + // vault ata address is writable + accountFlags[7] = bytes1(0x01); // true + // system program id is not writable + accountFlags[8] = bytes1(0x00); // false + // token program id is not writable + accountFlags[9] = bytes1(0x00); // false + // associated token program id is not writable + accountFlags[10] = bytes1(0x00); // false + + // withdraw instruction discriminator + bytes8 instructionDiscriminator = 0xb712469c946da122; + + bytes[] memory functionArguments = new bytes[](2); + bool isNative = false; + // here on purpose we do not convert to uint64 as feesPlug withdraw function expects uint256 + uint64 withdrawAmountU64 = convertToSolanaUint64(withdrawAmount_); + functionArguments[0] = abi.encode(withdrawAmountU64); + functionArguments[1] = abi.encode(isNative ? 1 : 0); + + string[] memory functionArgumentTypeNames = new string[](2); + functionArgumentTypeNames[0] = "u64"; // this should fit most of the cases (but our BorshEncoder supports max 128 bits) + functionArgumentTypeNames[1] = "u8"; // bool is encoded as 1 or 0 + + return + SolanaInstruction({ + data: SolanaInstructionData({ + programId: feesPlugSolanaProgramId, + instructionDiscriminator: instructionDiscriminator, + accounts: accounts, + functionArguments: functionArguments + }), + description: SolanaInstructionDataDescription({ + accountFlags: accountFlags, + functionArgumentTypeNames: functionArgumentTypeNames + }) + }); + } +} + +// convert EVM uint256 18 decimals to Solana uint64 6 decimals +function convertToSolanaUint64(uint256 amount) pure returns (uint64) { + uint256 scaledAmount = amount / 10 ** 12; + require(scaledAmount <= type(uint64).max, "Amount exceeds uint64 max"); + return uint64(scaledAmount); } diff --git a/contracts/evmx/fees/FeesManager.sol b/contracts/evmx/fees/FeesManager.sol index ee999a68..79dbc389 100644 --- a/contracts/evmx/fees/FeesManager.sol +++ b/contracts/evmx/fees/FeesManager.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.21; import "./Credit.sol"; +import {ForwarderSolana} from "../helpers/ForwarderSolana.sol"; /// @title FeesManager /// @notice Contract for managing fees @@ -50,7 +51,8 @@ contract FeesManager is Credit { address feesPool_, address owner_, uint256 fees_, - bytes32 sbType_ + bytes32 sbType_, + address forwarderSolana_ ) public reinitializer(2) { evmxSlug = evmxSlug_; feesPool = IFeesPool(feesPool_); @@ -59,8 +61,18 @@ contract FeesManager is Credit { _initializeOwner(owner_); _initializeAppGateway(addressResolver_); + forwarderSolana = ForwarderSolana(forwarderSolana_); } + function setFeesPlugSolanaProgramId(bytes32 feesPlugSolanaProgramId_) external onlyOwner { + feesPlugSolanaProgramId = feesPlugSolanaProgramId_; + } + + function setSusdcSolanaProgramId(bytes32 susdcSolanaProgramId_) external onlyOwner { + susdcSolanaProgramId = susdcSolanaProgramId_; + } + + function setChainMaxFees( uint32[] calldata chainSlugs_, uint256[] calldata maxFees_ diff --git a/contracts/evmx/helpers/ForwarderSolana.sol b/contracts/evmx/helpers/ForwarderSolana.sol new file mode 100644 index 00000000..3d9b4df0 --- /dev/null +++ b/contracts/evmx/helpers/ForwarderSolana.sol @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "solady/utils/Initializable.sol"; +import "./AddressResolverUtil.sol"; +import "../interfaces/IAppGateway.sol"; +import "../interfaces/IForwarder.sol"; +import {RawPayload, OverrideParams, Transaction} from "../../utils/common/Structs.sol"; +import {AsyncModifierNotSet, WatcherNotSet, InvalidOnChainAddress} from "../../utils/common/Errors.sol"; +import "../../utils/RescueFundsLib.sol"; +import {toBytes32Format} from "../../utils/common/Converters.sol"; +import {SolanaInstruction} from "../../utils/common/Structs.sol"; +import {CHAIN_SLUG_SOLANA_MAINNET, CHAIN_SLUG_SOLANA_DEVNET} from "../../utils/common/Constants.sol"; +import {ForwarderStorage} from "./Forwarder.sol"; + +/// @title Forwarder Contract +/// @notice This contract acts as a forwarder for async calls to the on-chain contracts. +contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil { + error InvalidSolanaChainSlug(); + error AddressResolverNotSet(); + + constructor() { + _disableInitializers(); // disable for implementation + } + + /// @notice Initializer to replace constructor for upgradeable contracts + /// @param chainSlug_ chain slug on which the contract is deployed + //// @param onChainAddress_ on-chain address associated with this forwarder + /// @param addressResolver_ address resolver contract + function initialize( + uint32 chainSlug_, + bytes32 onChainAddress_, // TODO:GW: after demo remove this param, we take target as param in callSolana() + address addressResolver_ + ) public initializer { + if (chainSlug_ == CHAIN_SLUG_SOLANA_MAINNET || chainSlug_ == CHAIN_SLUG_SOLANA_DEVNET) { + chainSlug = chainSlug_; + } else { + revert InvalidSolanaChainSlug(); + } + onChainAddress = onChainAddress_; + _setAddressResolver(addressResolver_); + } + + /// @notice Returns the on-chain address associated with this forwarder. + /// @return The on-chain address. + function getOnChainAddress() external view returns (bytes32) { + return onChainAddress; + } + + /// @notice Returns the chain slug on which the contract is deployed. + /// @return chain slug + function getChainSlug() external view returns (uint32) { + return chainSlug; + } + + /** + * @notice Rescues funds from the contract if they are locked by mistake. This contract does not + * theoretically need this function but it is added for safety. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } + + /// @notice Fallback function to process the contract calls to onChainAddress + /// @dev It queues the calls in the middleware and deploys the promise contract + function callSolana( + bytes memory solanaPayload, + bytes32 target, + address callerAppGateway + ) external { + if (address(addressResolver__) == address(0)) { + revert AddressResolverNotSet(); + } + if (address(watcher__()) == address(0)) { + revert WatcherNotSet(); + } + + // validates if the async modifier is set + // address msgSender = msg.sender; + address msgSender = callerAppGateway; + bool isAsyncModifierSet = IAppGateway(msgSender).isAsyncModifierSet(); + if (!isAsyncModifierSet) revert AsyncModifierNotSet(); + + // fetch the override params from app gateway + OverrideParams memory overrideParams = IAppGateway(msgSender).getOverrideParams(); + + // Queue the call in the middleware. + RawPayload memory rawPayload; + rawPayload.overrideParams = overrideParams; + rawPayload.transaction = Transaction({ + chainSlug: chainSlug, + // target: onChainAddress, // for Solana reads it should be accountToRead + // TODO: Solana forwarder can be a singleton - does not need to store onChainAddress and can use target as param + target: target, + payload: solanaPayload + }); + watcher__().addPayloadData(rawPayload, msgSender); + } +} diff --git a/contracts/evmx/helpers/solana-utils/Ed25519.sol b/contracts/evmx/helpers/solana-utils/Ed25519.sol new file mode 100644 index 00000000..e4420b3d --- /dev/null +++ b/contracts/evmx/helpers/solana-utils/Ed25519.sol @@ -0,0 +1,906 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.21; + +import "./Sha512.sol"; +import "./Ed25519_pow.sol"; + +library Ed25519 { + function verify(bytes32 k, bytes32 r, bytes32 s, bytes memory m) internal pure returns (bool) { + unchecked { + uint256 hh; + // Step 1: compute SHA-512(R, A, M) + { + bytes memory rs = new bytes(k.length + r.length + m.length); + for (uint256 i = 0; i < r.length; i++) { + rs[i] = r[i]; + } + for (uint256 i = 0; i < k.length; i++) { + rs[i + 32] = k[i]; + } + for (uint256 i = 0; i < m.length; i++) { + rs[i + 64] = m[i]; + } + uint64[8] memory result = Sha512.hash(rs); + + uint256 h0 = uint256(result[0]) | + (uint256(result[1]) << 64) | + (uint256(result[2]) << 128) | + (uint256(result[3]) << 192); + + h0 = + ((h0 & + 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << + 8) | + ((h0 & + 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> + 8); + h0 = + ((h0 & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << + 16) | + ((h0 & + 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> + 16); + h0 = + ((h0 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << + 32) | + ((h0 & + 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> + 32); + + uint256 h1 = uint256(result[4]) | + (uint256(result[5]) << 64) | + (uint256(result[6]) << 128) | + (uint256(result[7]) << 192); + + h1 = + ((h1 & + 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << + 8) | + ((h1 & + 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> + 8); + h1 = + ((h1 & 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << + 16) | + ((h1 & + 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> + 16); + h1 = + ((h1 & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << + 32) | + ((h1 & + 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> + 32); + hh = addmod( + h0, + mulmod( + h1, + 0xfffffff_ffffffff_ffffffff_fffffffe_c6ef5bf4_737dcf70_d6ec3174_8d98951d, + 0x10000000_00000000_00000000_00000000_14def9de_a2f79cd6_5812631a_5cf5d3ed + ), + 0x10000000_00000000_00000000_00000000_14def9de_a2f79cd6_5812631a_5cf5d3ed + ); + } + // Step 2: unpack k + k = bytes32( + ((uint256(k) & + 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) | + ((uint256(k) & + 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> + 8) + ); + k = bytes32( + ((uint256(k) & + 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) | + ((uint256(k) & + 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> + 16) + ); + k = bytes32( + ((uint256(k) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << + 32) | + ((uint256(k) & + 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> + 32) + ); + k = bytes32( + ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) | + ((uint256(k) & + 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> + 64) + ); + k = bytes32((uint256(k) << 128) | (uint256(k) >> 128)); + uint256 ky = uint256(k) & + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff; + uint256 kx; + { + uint256 ky2 = mulmod( + ky, + ky, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 u = addmod( + ky2, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffec, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 v = mulmod( + ky2, + 0x52036cee_2b6ffe73_8cc74079_7779e898_00700a4d_4141d8ab_75eb4dca_135978a3, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ) + 1; + uint256 t = mulmod( + u, + v, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + (kx, ) = Ed25519_pow.pow22501(t); + kx = mulmod( + kx, + kx, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + kx = mulmod( + u, + mulmod( + mulmod( + kx, + kx, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ), + t, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ), + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + t = mulmod( + mulmod( + kx, + kx, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ), + v, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + if (t != u) { + if ( + t != + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - + u + ) { + return false; + } + kx = mulmod( + kx, + 0x2b832480_4fc1df0b_2b4d0099_3dfbd7a7_2f431806_ad2fe478_c4ee1b27_4a0ea0b0, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + } + if ((kx & 1) != uint256(k) >> 255) { + kx = 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - kx; + } + // Verify s + s = bytes32( + ((uint256(s) & + 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) | + ((uint256(s) & + 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> + 8) + ); + s = bytes32( + ((uint256(s) & + 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) | + ((uint256(s) & + 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> + 16) + ); + s = bytes32( + ((uint256(s) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << + 32) | + ((uint256(s) & + 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> + 32) + ); + s = bytes32( + ((uint256(s) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) | + ((uint256(s) & + 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> + 64) + ); + s = bytes32((uint256(s) << 128) | (uint256(s) >> 128)); + if ( + uint256(s) >= + 0x10000000_00000000_00000000_00000000_14def9de_a2f79cd6_5812631a_5cf5d3ed + ) { + return false; + } + uint256 vx; + uint256 vu; + uint256 vy; + uint256 vv; + // Step 3: compute multiples of k + uint256[8][3][2] memory tables; + { + uint256 ks = ky + kx; + uint256 kd = ky + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - + kx; + uint256 k2dt = mulmod( + mulmod( + kx, + ky, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ), + 0x2406d9dc_56dffce7_198e80f2_eef3d130_00e0149a_8283b156_ebd69b94_26b2f159, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 kky = ky; + uint256 kkx = kx; + uint256 kku = 1; + uint256 kkv = 1; + { + uint256 xx = mulmod( + kkx, + kkv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 yy = mulmod( + kky, + kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 zz = mulmod( + kku, + kkv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 xx2 = mulmod( + xx, + xx, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 yy2 = mulmod( + yy, + yy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 xxyy = mulmod( + xx, + yy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 zz2 = mulmod( + zz, + zz, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + kkx = xxyy + xxyy; + kku = + yy2 - + xx2 + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + kky = xx2 + yy2; + kkv = addmod( + zz2 + zz2, + 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - + kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + { + uint256 xx = mulmod( + kkx, + kkv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 yy = mulmod( + kky, + kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 zz = mulmod( + kku, + kkv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 xx2 = mulmod( + xx, + xx, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 yy2 = mulmod( + yy, + yy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 xxyy = mulmod( + xx, + yy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 zz2 = mulmod( + zz, + zz, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + kkx = xxyy + xxyy; + kku = + yy2 - + xx2 + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + kky = xx2 + yy2; + kkv = addmod( + zz2 + zz2, + 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - + kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + { + uint256 xx = mulmod( + kkx, + kkv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 yy = mulmod( + kky, + kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 zz = mulmod( + kku, + kkv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 xx2 = mulmod( + xx, + xx, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 yy2 = mulmod( + yy, + yy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 xxyy = mulmod( + xx, + yy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 zz2 = mulmod( + zz, + zz, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + kkx = xxyy + xxyy; + kku = + yy2 - + xx2 + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + kky = xx2 + yy2; + kkv = addmod( + zz2 + zz2, + 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - + kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 cprod = 1; + uint256[8][3][2] memory tables_ = tables; + for (uint256 i = 0; ; i++) { + uint256 cs; + uint256 cd; + uint256 ct; + uint256 c2z; + { + uint256 cx = mulmod( + kkx, + kkv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 cy = mulmod( + kky, + kku, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 cz = mulmod( + kku, + kkv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + ct = mulmod( + kkx, + kky, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + cs = cy + cx; + cd = + cy - + cx + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + c2z = cz + cz; + } + tables_[1][0][i] = cs; + tables_[1][1][i] = cd; + tables_[1][2][i] = mulmod( + ct, + 0x2406d9dc_56dffce7_198e80f2_eef3d130_00e0149a_8283b156_ebd69b94_26b2f159, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + tables_[0][0][i] = c2z; + tables_[0][1][i] = cprod; + cprod = mulmod( + cprod, + c2z, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + if (i == 7) { + break; + } + uint256 ab = mulmod( + cs, + ks, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 aa = mulmod( + cd, + kd, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ac = mulmod( + ct, + k2dt, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + kkx = + ab - + aa + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + kku = addmod( + c2z, + ac, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + kky = ab + aa; + kkv = addmod( + c2z, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed - + ac, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 t; + (cprod, t) = Ed25519_pow.pow22501(cprod); + cprod = mulmod( + cprod, + cprod, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + cprod = mulmod( + cprod, + cprod, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + cprod = mulmod( + cprod, + cprod, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + cprod = mulmod( + cprod, + cprod, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + cprod = mulmod( + cprod, + cprod, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + cprod = mulmod( + cprod, + t, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + for (uint256 i = 7; ; i--) { + uint256 cinv = mulmod( + cprod, + tables_[0][1][i], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + tables_[1][0][i] = mulmod( + tables_[1][0][i], + cinv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + tables_[1][1][i] = mulmod( + tables_[1][1][i], + cinv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + tables_[1][2][i] = mulmod( + tables_[1][2][i], + cinv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + if (i == 0) { + break; + } + cprod = mulmod( + cprod, + tables_[0][0][i], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + tables_[0] = [ + [ + 0x43e7ce9d_19ea5d32_9385a44c_321ea161_67c996e3_7dc6070c_97de49e3_7ac61db9, + 0x40cff344_25d8ec30_a3bb74ba_58cd5854_fa1e3818_6ad0d31e_bc8ae251_ceb2c97e, + 0x459bd270_46e8dd45_aea7008d_b87a5a8f_79067792_53d64523_58951859_9fdfbf4b, + 0x69fdd1e2_8c23cc38_94d0c8ff_90e76f6d_5b6e4c2e_620136d0_4dd83c4a_51581ab9, + 0x54dceb34_13ce5cfa_11196dfc_960b6eda_f4b380c6_d4d23784_19cc0279_ba49c5f3, + 0x4e24184d_d71a3d77_eef3729f_7f8cf7c1_7224cf40_aa7b9548_b9942f3c_5084ceed, + 0x5a0e5aab_20262674_ae117576_1cbf5e88_9b52a55f_d7ac5027_c228cebd_c8d2360a, + 0x26239334_073e9b38_c6285955_6d451c3d_cc8d30e8_4b361174_f488eadd_e2cf17d9 + ], + [ + 0x227e97c9_4c7c0933_d2e0c21a_3447c504_fe9ccf82_e8a05f59_ce881c82_eba0489f, + 0x226a3e0e_cc4afec6_fd0d2884_13014a9d_bddecf06_c1a2f0bb_702ba77c_613d8209, + 0x34d7efc8_51d45c5e_71efeb0f_235b7946_91de6228_877569b3_a8d52bf0_58b8a4a0, + 0x3c1f5fb3_ca7166fc_e1471c9b_752b6d28_c56301ad_7b65e845_1b2c8c55_26726e12, + 0x6102416c_f02f02ff_5be75275_f55f28db_89b2a9d2_456b860c_e22fc0e5_031f7cc5, + 0x40adf677_f1bfdae0_57f0fd17_9c126179_18ddaa28_91a6530f_b1a4294f_a8665490, + 0x61936f3c_41560904_6187b8ba_a978cbc9_b4789336_3ae5a3cc_7d909f36_35ae7f48, + 0x562a9662_b6ec47f9_e979d473_c02b51e4_42336823_8c58ddb5_2f0e5c6a_180e6410 + ], + [ + 0x3788bdb4_4f8632d4_2d0dbee5_eea1acc6_136cf411_e655624f_55e48902_c3bd5534, + 0x6190cf2c_2a7b5ad7_69d594a8_2844f23b_4167fa7c_8ac30e51_aa6cfbeb_dcd4b945, + 0x65f77870_96be9204_123a71f3_ac88a87b_e1513217_737d6a1e_2f3a13a4_3d7e3a9a, + 0x23af32d_bfa67975_536479a7_a7ce74a0_2142147f_ac048018_7f1f1334_9cda1f2d, + 0x64fc44b7_fc6841bd_db0ced8b_8b0fe675_9137ef87_ee966512_15fc1dbc_d25c64dc, + 0x1434aa37_48b701d5_b69df3d7_d340c1fe_3f6b9c1e_fc617484_caadb47e_382f4475, + 0x457a6da8_c962ef35_f2b21742_3e5844e9_d2353452_7e8ea429_0d24e3dd_f21720c6, + 0x63b9540c_eb60ccb5_1e4d989d_956e053c_f2511837_efb79089_d2ff4028_4202c53d + ] + ]; + } + // Step 4: compute s*G - h*A + { + uint256 ss = uint256(s) << 3; + uint256 hhh = hh + + 0x80000000_00000000_00000000_00000000_a6f7cef5_17bce6b2_c09318d2_e7ae9f60; + uint256 vvx = 0; + uint256 vvu = 1; + uint256 vvy = 1; + uint256 vvv = 1; + for (uint256 i = 252; ; i--) { + uint256 bit = 8 << i; + if ((ss & bit) != 0) { + uint256 ws; + uint256 wd; + uint256 wz; + uint256 wt; + { + uint256 wx = mulmod( + vvx, + vvv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 wy = mulmod( + vvy, + vvu, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + ws = wy + wx; + wd = + wy - + wx + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + wz = mulmod( + vvu, + vvv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + wt = mulmod( + vvx, + vvy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 j = (ss >> i) & 7; + ss &= ~(7 << i); + uint256[8][3][2] memory tables_ = tables; + uint256 aa = mulmod( + wd, + tables_[0][1][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ab = mulmod( + ws, + tables_[0][0][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ac = mulmod( + wt, + tables_[0][2][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vvx = + ab - + aa + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvu = wz + ac; + vvy = ab + aa; + vvv = + wz - + ac + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + } + if ((hhh & bit) != 0) { + uint256 ws; + uint256 wd; + uint256 wz; + uint256 wt; + { + uint256 wx = mulmod( + vvx, + vvv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 wy = mulmod( + vvy, + vvu, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + ws = wy + wx; + wd = + wy - + wx + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + wz = mulmod( + vvu, + vvv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + wt = mulmod( + vvx, + vvy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 j = (hhh >> i) & 7; + hhh &= ~(7 << i); + uint256[8][3][2] memory tables_ = tables; + uint256 aa = mulmod( + wd, + tables_[1][0][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ab = mulmod( + ws, + tables_[1][1][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ac = mulmod( + wt, + tables_[1][2][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vvx = + ab - + aa + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvu = + wz - + ac + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvy = ab + aa; + vvv = wz + ac; + } + if (i == 0) { + uint256 ws; + uint256 wd; + uint256 wz; + uint256 wt; + { + uint256 wx = mulmod( + vvx, + vvv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 wy = mulmod( + vvy, + vvu, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + ws = wy + wx; + wd = + wy - + wx + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + wz = mulmod( + vvu, + vvv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + wt = mulmod( + vvx, + vvy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + uint256 j = hhh & 7; + uint256[8][3][2] memory tables_ = tables; + uint256 aa = mulmod( + wd, + tables_[1][0][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ab = mulmod( + ws, + tables_[1][1][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 ac = mulmod( + wt, + tables_[1][2][j], + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vvx = + ab - + aa + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvu = + wz - + ac + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvy = ab + aa; + vvv = wz + ac; + break; + } + { + uint256 xx = mulmod( + vvx, + vvv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 yy = mulmod( + vvy, + vvu, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 zz = mulmod( + vvu, + vvv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 xx2 = mulmod( + xx, + xx, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 yy2 = mulmod( + yy, + yy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 xxyy = mulmod( + xx, + yy, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 zz2 = mulmod( + zz, + zz, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vvx = xxyy + xxyy; + vvu = + yy2 - + xx2 + + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + vvy = xx2 + yy2; + vvv = addmod( + zz2 + zz2, + 0xffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffda - + vvu, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } + } + vx = vvx; + vu = vvu; + vy = vvy; + vv = vvv; + } + // Step 5: compare the points + (uint256 vi, uint256 vj) = Ed25519_pow.pow22501( + mulmod( + vu, + vv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ) + ); + vi = mulmod( + vi, + vi, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vi = mulmod( + vi, + vi, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vi = mulmod( + vi, + vi, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vi = mulmod( + vi, + vi, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vi = mulmod( + vi, + vi, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vi = mulmod( + vi, + vj, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vx = mulmod( + vx, + mulmod( + vi, + vv, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ), + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + vy = mulmod( + vy, + mulmod( + vi, + vu, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ), + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + bytes32 vr = bytes32(vy | (vx << 255)); + vr = bytes32( + ((uint256(vr) & + 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) | + ((uint256(vr) & + 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> + 8) + ); + vr = bytes32( + ((uint256(vr) & + 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) | + ((uint256(vr) & + 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> + 16) + ); + vr = bytes32( + ((uint256(vr) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << + 32) | + ((uint256(vr) & + 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> + 32) + ); + vr = bytes32( + ((uint256(vr) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) | + ((uint256(vr) & + 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> + 64) + ); + vr = bytes32((uint256(vr) << 128) | (uint256(vr) >> 128)); + + return vr == r; + } + } +} \ No newline at end of file diff --git a/contracts/evmx/helpers/solana-utils/Ed25519_pow.sol b/contracts/evmx/helpers/solana-utils/Ed25519_pow.sol new file mode 100644 index 00000000..3af4725a --- /dev/null +++ b/contracts/evmx/helpers/solana-utils/Ed25519_pow.sol @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.21; + +library Ed25519_pow { + // Computes (v^(2^250-1), v^11) mod p + function pow22501(uint256 v) internal pure returns (uint256 p22501, uint256 p11) { + p11 = mulmod( + v, + v, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + p22501 = mulmod( + p11, + p11, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + p22501 = mulmod( + mulmod( + p22501, + p22501, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ), + v, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + p11 = mulmod( + p22501, + p11, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + p22501 = mulmod( + mulmod( + p11, + p11, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ), + p22501, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 a = mulmod( + p22501, + p22501, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + p22501 = mulmod( + p22501, + a, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + a = mulmod( + p22501, + p22501, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod( + p22501, + a, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + uint256 b = mulmod( + a, + a, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + p22501 = mulmod( + p22501, + a, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + a = mulmod( + p22501, + p22501, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod( + p22501, + a, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + b = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + b = mulmod(b, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, b, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + a = mulmod(a, a, 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed); + p22501 = mulmod( + p22501, + a, + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed + ); + } +} \ No newline at end of file diff --git a/contracts/evmx/helpers/solana-utils/Sha512.sol b/contracts/evmx/helpers/solana-utils/Sha512.sol new file mode 100644 index 00000000..7f04221a --- /dev/null +++ b/contracts/evmx/helpers/solana-utils/Sha512.sol @@ -0,0 +1,278 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.21; + +// Reference: https://csrc.nist.gov/csrc/media/publications/fips/180/2/archive/2002-08-01/documents/fips180-2.pdf + +library Sha512 { + // @notice: The message, M, shall be padded before hash computation begins. + // The purpose of this padding is to ensure that the padded message is a multiple of 1024 bits. + // @param message input raw message bytes + // @return padded message bytes + function preprocess(bytes memory message) internal pure returns (bytes memory) { + uint256 padding = 128 - (message.length % 128); + if (message.length % 128 >= 112) { + padding = 256 - (message.length % 128); + } + bytes memory result = new bytes(message.length + padding); + + for (uint256 i = 0; i < message.length; i++) { + result[i] = message[i]; + } + result[message.length] = 0x80; + + uint128 bitSize = uint128(message.length * 8); + bytes memory bitlength = abi.encodePacked(bitSize); + for (uint256 index = 0; index < bitlength.length; index++) { + result[result.length - 1 - index] = bitlength[bitlength.length - 1 - index]; + } + return result; + } + + function bytesToBytes8(bytes memory b, uint256 offset) internal pure returns (bytes8) { + bytes8 out; + for (uint256 i = 0; i < 8; i++) { + out |= bytes8(b[offset + i] & 0xFF) >> (i * 8); + } + return out; + } + + function cutBlock( + bytes memory data, + uint256 blockIndex + ) internal pure returns (uint64[16] memory) { + uint64[16] memory result; + for (uint8 r = 0; r < result.length; r++) { + result[r] = uint64(bytesToBytes8(data, blockIndex * 128 + r * 8)); + } + return result; + } + + // This section defines the functions that are used by sha-512. + // https://csrc.nist.gov/csrc/media/publications/fips/180/2/archive/2002-08-01/documents/fips180-2.pdf#page=15 + + // @notice: Thus, ROTR(x, n) is equivalent to a circular shift (rotation) of x by n positions to the right. + // @param x input num + // @param n num of positions to circular shift + // @return uint64 + function ROTR(uint64 x, uint256 n) internal pure returns (uint64) { + return (x << (64 - uint64(n))) + (x >> n); + } + + // @notice: The right shift operation SHR n(x), where x is a w-bit word and n is an integer with 0 <= n < w, is defined by SHR(x, n) = x >> n. + // @param x input num + // @param n num of positions to shift + // @return uint64 + function SHR(uint64 x, uint256 n) internal pure returns (uint64) { + return uint64(x >> n); + } + + // @notice: Ch(x, y, z) = (x ^ y) ⊕ (﹁ x ^ z) + // @param x x + // @param y y + // @param z z + // @return uint64 + function Ch(uint64 x, uint64 y, uint64 z) internal pure returns (uint64) { + return (x & y) ^ ((x ^ 0xffffffffffffffff) & z); + } + + // @notice: Maj(x, y, z) = (x ^ y) ⊕ (x ^ z) ⊕ (y ^ z) + // @param x x + // @param y y + // @param z z + // @return uint64 + function Maj(uint64 x, uint64 y, uint64 z) internal pure returns (uint64) { + return (x & y) ^ (x & z) ^ (y & z); + } + + // @notice: sigma0(x) = ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39) + // @param x x + // @return uint64 + function sigma0(uint64 x) internal pure returns (uint64) { + return ROTR(x, 28) ^ ROTR(x, 34) ^ ROTR(x, 39); + } + + // @notice: sigma1(x) = ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41) + // @param x x + // @return uint64 + function sigma1(uint64 x) internal pure returns (uint64) { + return ROTR(x, 14) ^ ROTR(x, 18) ^ ROTR(x, 41); + } + + // @notice: gamma0(x) = OTR(x, 1) ^ ROTR(x, 8) ^ SHR(x, 7) + // @param x x + // @return uint64 + function gamma0(uint64 x) internal pure returns (uint64) { + return ROTR(x, 1) ^ ROTR(x, 8) ^ SHR(x, 7); + } + + // @notice: gamma1(x) = ROTR(x, 19) ^ ROTR(x, 61) ^ SHR(x, 6) + // @param x x + // @return uint64 + function gamma1(uint64 x) internal pure returns (uint64) { + return ROTR(x, 19) ^ ROTR(x, 61) ^ SHR(x, 6); + } + + struct FuncVar { + uint64 a; + uint64 b; + uint64 c; + uint64 d; + uint64 e; + uint64 f; + uint64 g; + uint64 h; + } + + // @notice Calculate the SHA512 of input data. + // @param data input data bytes + // @return 512 bits hash result + function hash(bytes memory data) internal pure returns (uint64[8] memory) { + uint64[8] memory H = [ + 0x6a09e667f3bcc908, + 0xbb67ae8584caa73b, + 0x3c6ef372fe94f82b, + 0xa54ff53a5f1d36f1, + 0x510e527fade682d1, + 0x9b05688c2b3e6c1f, + 0x1f83d9abfb41bd6b, + 0x5be0cd19137e2179 + ]; + + unchecked { + uint64 T1; + uint64 T2; + + uint64[80] memory W; + FuncVar memory fvar; + + uint64[80] memory K = [ + 0x428a2f98d728ae22, + 0x7137449123ef65cd, + 0xb5c0fbcfec4d3b2f, + 0xe9b5dba58189dbbc, + 0x3956c25bf348b538, + 0x59f111f1b605d019, + 0x923f82a4af194f9b, + 0xab1c5ed5da6d8118, + 0xd807aa98a3030242, + 0x12835b0145706fbe, + 0x243185be4ee4b28c, + 0x550c7dc3d5ffb4e2, + 0x72be5d74f27b896f, + 0x80deb1fe3b1696b1, + 0x9bdc06a725c71235, + 0xc19bf174cf692694, + 0xe49b69c19ef14ad2, + 0xefbe4786384f25e3, + 0x0fc19dc68b8cd5b5, + 0x240ca1cc77ac9c65, + 0x2de92c6f592b0275, + 0x4a7484aa6ea6e483, + 0x5cb0a9dcbd41fbd4, + 0x76f988da831153b5, + 0x983e5152ee66dfab, + 0xa831c66d2db43210, + 0xb00327c898fb213f, + 0xbf597fc7beef0ee4, + 0xc6e00bf33da88fc2, + 0xd5a79147930aa725, + 0x06ca6351e003826f, + 0x142929670a0e6e70, + 0x27b70a8546d22ffc, + 0x2e1b21385c26c926, + 0x4d2c6dfc5ac42aed, + 0x53380d139d95b3df, + 0x650a73548baf63de, + 0x766a0abb3c77b2a8, + 0x81c2c92e47edaee6, + 0x92722c851482353b, + 0xa2bfe8a14cf10364, + 0xa81a664bbc423001, + 0xc24b8b70d0f89791, + 0xc76c51a30654be30, + 0xd192e819d6ef5218, + 0xd69906245565a910, + 0xf40e35855771202a, + 0x106aa07032bbd1b8, + 0x19a4c116b8d2d0c8, + 0x1e376c085141ab53, + 0x2748774cdf8eeb99, + 0x34b0bcb5e19b48a8, + 0x391c0cb3c5c95a63, + 0x4ed8aa4ae3418acb, + 0x5b9cca4f7763e373, + 0x682e6ff3d6b2b8a3, + 0x748f82ee5defb2fc, + 0x78a5636f43172f60, + 0x84c87814a1f0ab72, + 0x8cc702081a6439ec, + 0x90befffa23631e28, + 0xa4506cebde82bde9, + 0xbef9a3f7b2c67915, + 0xc67178f2e372532b, + 0xca273eceea26619c, + 0xd186b8c721c0c207, + 0xeada7dd6cde0eb1e, + 0xf57d4f7fee6ed178, + 0x06f067aa72176fba, + 0x0a637dc5a2c898a6, + 0x113f9804bef90dae, + 0x1b710b35131c471b, + 0x28db77f523047d84, + 0x32caab7b40c72493, + 0x3c9ebe0a15c9bebc, + 0x431d67c49c100d4c, + 0x4cc5d4becb3e42b6, + 0x597f299cfc657e2a, + 0x5fcb6fab3ad6faec, + 0x6c44198c4a475817 + ]; + + bytes memory blocks = preprocess(data); + + for (uint256 j = 0; j < blocks.length / 128; j++) { + uint64[16] memory M = cutBlock(blocks, j); + + fvar.a = H[0]; + fvar.b = H[1]; + fvar.c = H[2]; + fvar.d = H[3]; + fvar.e = H[4]; + fvar.f = H[5]; + fvar.g = H[6]; + fvar.h = H[7]; + + for (uint256 i = 0; i < 80; i++) { + if (i < 16) { + W[i] = M[i]; + } else { + W[i] = gamma1(W[i - 2]) + W[i - 7] + gamma0(W[i - 15]) + W[i - 16]; + } + + T1 = fvar.h + sigma1(fvar.e) + Ch(fvar.e, fvar.f, fvar.g) + K[i] + W[i]; + T2 = sigma0(fvar.a) + Maj(fvar.a, fvar.b, fvar.c); + + fvar.h = fvar.g; + fvar.g = fvar.f; + fvar.f = fvar.e; + fvar.e = fvar.d + T1; + fvar.d = fvar.c; + fvar.c = fvar.b; + fvar.b = fvar.a; + fvar.a = T1 + T2; + } + + H[0] = H[0] + fvar.a; + H[1] = H[1] + fvar.b; + H[2] = H[2] + fvar.c; + H[3] = H[3] + fvar.d; + H[4] = H[4] + fvar.e; + H[5] = H[5] + fvar.f; + H[6] = H[6] + fvar.g; + H[7] = H[7] + fvar.h; + } + } + + return H; + } +} \ No newline at end of file diff --git a/contracts/evmx/helpers/solana-utils/SolanaPda.sol b/contracts/evmx/helpers/solana-utils/SolanaPda.sol new file mode 100644 index 00000000..1a276334 --- /dev/null +++ b/contracts/evmx/helpers/solana-utils/SolanaPda.sol @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import "./Ed25519_pow.sol"; + +// TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA +bytes32 constant TOKEN_PROGRAM_ID = 0x06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9; +// TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb +bytes32 constant TOKEN_2022_PROGRAM_ID = 0x06ddf6e1ee758fde18425dbce46ccddab61afc4d83b90d27febdf928d8a18bfc; +// ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL +bytes32 constant ASSOCIATED_TOKEN_PROGRAM_ID = 0x8c97258f4e2489f1bb3d1029148e0d830b5a1399daff1084048e7bd8dbe9f859; +// 11111111111111111111111111111111 +bytes32 constant SYSTEM_PROGRAM_ID = 0x0000000000000000000000000000000000000000000000000000000000000000; + +/* + * - programId and PDAs are passed as raw 32 bytes (same order as Solana's Pubkey.to_bytes()). + * - Seeds: <= 16, each <= 32 bytes. The bump is the final one-byte seed. + * - PDA derivation: sha256( seeds || bump || programId || "ProgramDerivedAddress" ), + * and the result MUST be OFF the Ed25519 curve (i.e., the 32B value does NOT decode + * to a valid Ed25519 point). + */ + +library Ed25519CurveUtils { + // p = 2^255 - 19 + uint256 internal constant P = + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed; + + // d = -121665/121666 mod p (RFC 8032 / Edwards25519) + uint256 internal constant D = + 0x52036cee2b6ffe738cc740797779e89800700a4d4141d8ab75eb4dca135978a3; + + // (p-1)/2 for Legendre symbol (quadratic residue test) + uint256 internal constant P_MINUS_1_OVER_2 = + 0x3fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6e; + + // p-2 for modular inverse via Fermat + uint256 internal constant P_MINUS_2 = + 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeb; + + function _toBeBytes(uint256 x) private pure returns (bytes32 b) { + b = bytes32(x); + } + + function _fromBeBytes(bytes memory b) private pure returns (uint256 x) { + require(b.length == 32, "bad size"); + assembly { + x := mload(add(b, 0x20)) + } + } + + /// @dev modular exponentiation via precompile 0x05 + function _modexp(uint256 base, uint256 exp, uint256 modn) private view returns (uint256 r) { + bytes memory input = abi.encodePacked( + uint256(32), + uint256(32), + uint256(32), + _toBeBytes(base), + _toBeBytes(exp), + _toBeBytes(modn) + ); + bytes memory output = new bytes(32); + bool ok; + assembly { + ok := staticcall(gas(), 0x05, add(input, 0x20), mload(input), add(output, 0x20), 32) + } + require(ok, "modexp failed"); + r = _fromBeBytes(output); + } + + // based on Farcaster code + function decodesToValidPoint(bytes32 k) internal pure returns (bool) { + unchecked { + // Byte-swap to little-endian (your code) + k = bytes32( + ((uint256(k) & + 0xff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff_00ff00ff) << 8) | + ((uint256(k) & + 0xff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00_ff00ff00) >> + 8) + ); + k = bytes32( + ((uint256(k) & + 0xffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff_0000ffff) << 16) | + ((uint256(k) & + 0xffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000_ffff0000) >> + 16) + ); + k = bytes32( + ((uint256(k) & 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff) << + 32) | + ((uint256(k) & + 0xffffffff_00000000_ffffffff_00000000_ffffffff_00000000_ffffffff_00000000) >> + 32) + ); + k = bytes32( + ((uint256(k) & 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff) << 64) | + ((uint256(k) & + 0xffffffff_ffffffff_00000000_00000000_ffffffff_ffffffff_00000000_00000000) >> + 64) + ); + k = bytes32((uint256(k) << 128) | (uint256(k) >> 128)); + + // Extract y (clear sign bit) + uint256 ky = uint256(k) & + 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff; + + // --- IMPORTANT: Canonical check (reject non-canonical y) --- + if (ky >= 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed) { + return false; + } + + // Curve math + uint256 p = 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed; + uint256 d = 0x52036cee_2b6ffe73_8cc74079_7779e898_00700a4d_4141d8ab_75eb4dca_135978a3; + uint256 iSqrtM1 = 0x2b832480_4fc1df0b_2b4d0099_3dfbd7a7_2f431806_ad2fe478_c4ee1b27_4a0ea0b0; + + uint256 ky2 = mulmod(ky, ky, p); + uint256 u = addmod(ky2, p - 1, p); + uint256 v = addmod(mulmod(d, ky2, p), 1, p); + + uint256 t = mulmod(u, v, p); + (uint256 kx, ) = Ed25519_pow.pow22501(t); // addition chain sqrt helper + kx = mulmod(kx, kx, p); + kx = mulmod(u, mulmod(mulmod(kx, kx, p), t, p), p); + + t = mulmod(mulmod(kx, kx, p), v, p); + + // Check curve equation (two roots case) + if (t != u) { + if (t != p - u) return false; + kx = mulmod(kx, iSqrtM1, p); + } + + // Sign parity enforcement (not needed for PDA, but harmless) + if ((kx & 1) != (uint256(k) >> 255)) { + kx = p - kx; + } + + return true; + } + } +} + +library SolanaPDA { + bytes constant DOMAIN = "ProgramDerivedAddress"; + + /// Reproduces Solana's sha256(seeds || bump || programId || DOMAIN) + function _compute( + bytes32 programId, + bytes[] memory seeds, + uint8 bump + ) internal pure returns (bytes32) { + require(seeds.length <= 16, "too many seeds"); + uint256 total; + for (uint256 i; i < seeds.length; ++i) { + require(seeds[i].length <= 32, "seed too long"); + total += seeds[i].length; + } + + bytes memory buf = new bytes(total + 1 + 32 + DOMAIN.length); + uint256 p; + + // seeds + for (uint256 i; i < seeds.length; ++i) { + bytes memory s = seeds[i]; + for (uint256 j; j < s.length; ++j) { + buf[p++] = s[j]; + } + } + + // bump (as a single byte) + buf[p++] = bytes1(bump); + + // programId raw 32 bytes (as provided) + bytes32 pid = programId; + for (uint256 k; k < 32; ++k) { + buf[p++] = pid[k]; + } + + // domain separator + for (uint256 d; d < DOMAIN.length; ++d) { + buf[p++] = DOMAIN[d]; + } + + return sha256(buf); + } + + /// @notice Validate PDA like Solana create_program_address (explicit bump). + function validatePDA( + bytes32 programId, + bytes[] memory seeds, + uint8 bump, + bytes32 expectedPDA + ) internal pure returns (bool) { + bytes32 derived = _compute(programId, seeds, bump); + + // Must be OFF-curve + bool onCurve = Ed25519CurveUtils.decodesToValidPoint(derived); + require(!onCurve, "derived is on-curve"); + + require(derived == expectedPDA, "PDA mismatch"); + return true; + } + + /// Returns the canonical (pda, bump) like Solana's findProgramAddressSync: + /// finds the first OFF-curve result scanning from b=255 down to 0. + function findProgramAddress( + bytes32 programId, + bytes[] memory seeds + ) internal pure returns (bytes32 pda, uint8 bump) { + for (uint256 b = 255; ; ) { + bytes32 d = _compute(programId, seeds, uint8(b)); + if (!Ed25519CurveUtils.decodesToValidPoint(d)) { + return (d, uint8(b)); // off-curve => valid PDA + } + if (b == 0) revert("no valid PDA"); + unchecked { + --b; + } + } + } + + function deriveTokenAtaAddress( + bytes32 solanaAddress, + bytes32 mint + ) internal pure returns (bytes32) { + bytes[] memory seeds = new bytes[](3); + // user solanaAddress + seeds[0] = abi.encodePacked(solanaAddress); + // token programId + seeds[1] = abi.encodePacked(TOKEN_PROGRAM_ID); + // token mint + seeds[2] = abi.encodePacked(mint); + + (bytes32 pda /*uint8 bump*/, ) = SolanaPDA.findProgramAddress( + ASSOCIATED_TOKEN_PROGRAM_ID, + seeds + ); + return pda; + } + + /// Proves that (expectedPDA, bump) is the *canonical* PDA: + /// - derived(bump) equals expectedPDA and is OFF-curve + /// - for all b in (bump+1 .. 255): derived(b) is ON-curve (so no higher valid PDA exists) + function validatePDAWithCanonicalBump( + bytes32 programId, + bytes[] memory seeds, + uint8 bump, + bytes32 expectedPDA + ) internal pure returns (bool) { + // Must match create_program_address result and be OFF-curve + require(validatePDA(programId, seeds, bump, expectedPDA), "invalid PDA/bump"); + + // Prove canonicality: no valid PDA at any higher bump + if (bump < 255) { + for (uint256 b = 255; b > bump; ) { + bytes32 d = _compute(programId, seeds, uint8(b)); + // Each higher bump must decode as a valid point => ON-curve (invalid PDA) + require( + Ed25519CurveUtils.decodesToValidPoint(d), + "non-canonical: higher bump is off-curve" + ); + unchecked { + --b; + } + } + } + return true; + } +} \ No newline at end of file diff --git a/contracts/evmx/helpers/solana-utils/SolanaSignature.sol b/contracts/evmx/helpers/solana-utils/SolanaSignature.sol new file mode 100644 index 00000000..c143500b --- /dev/null +++ b/contracts/evmx/helpers/solana-utils/SolanaSignature.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.21; + +import {Ed25519} from "./Ed25519.sol"; + +contract SolanaSignature { + function verifyMessage( + bytes32 public_key, + bytes32 signature_r, + bytes32 signature_s, + bytes memory message + ) public pure returns (bool) { + bool valid = Ed25519.verify(public_key, signature_r, signature_s, message); + + return valid; + } +} \ No newline at end of file diff --git a/contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol b/contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol new file mode 100644 index 00000000..1480153e --- /dev/null +++ b/contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +import {SolanaPDA} from "../SolanaPda.sol"; + +library FeesPlugProgramPda { + // string: "config:" + bytes constant PROGRAM_CONFIG_SEED_PREFIX_HEX = hex"636f6e6669673a"; + // string: "vault_config:" + bytes constant VAULT_CONFIG_SEED_PREFIX_HEX = hex"7661756c745f636f6e6669673a"; + // string: "tmp_data:" + bytes constant TMP_DATA_SEED_PREFIX_HEX = hex"746d705f646174613a"; + // string: "whitelisted_token:" + bytes constant WHITELISTED_TOKEN_SEED_PREFIX_HEX = hex"77686974656c69737465645f746f6b656e3a"; + + function deriveProgramConfigPda(bytes32 programId) internal pure returns (bytes32, uint8) { + bytes[] memory seeds = new bytes[](1); + seeds[0] = PROGRAM_CONFIG_SEED_PREFIX_HEX; + + return SolanaPDA.findProgramAddress(programId, seeds); + } + + function deriveTmpReturnStoragePda(bytes32 programId) internal pure returns (bytes32, uint8) { + bytes[] memory seeds = new bytes[](1); + seeds[0] = TMP_DATA_SEED_PREFIX_HEX; + + return SolanaPDA.findProgramAddress(programId, seeds); + } + + function deriveWhitelistedTokenPda( + bytes32 programId, + bytes32 tokenMint + ) internal pure returns (bytes32, uint8) { + bytes[] memory seeds = new bytes[](2); + seeds[0] = WHITELISTED_TOKEN_SEED_PREFIX_HEX; + seeds[1] = abi.encodePacked(tokenMint); + + return SolanaPDA.findProgramAddress(programId, seeds); + } + + function deriveVaultConfigPda(bytes32 programId) internal pure returns (bytes32, uint8) { + bytes[] memory seeds = new bytes[](1); + seeds[0] = VAULT_CONFIG_SEED_PREFIX_HEX; + + return SolanaPDA.findProgramAddress(programId, seeds); + } +} \ No newline at end of file diff --git a/contracts/evmx/watcher/borsh-serde/BorshDecoder.sol b/contracts/evmx/watcher/borsh-serde/BorshDecoder.sol new file mode 100644 index 00000000..1c7671a3 --- /dev/null +++ b/contracts/evmx/watcher/borsh-serde/BorshDecoder.sol @@ -0,0 +1,359 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Based on Aurora bridge repo: https://github.com/aurora-is-near/aurora-contracts-sdk/blob/main/aurora-solidity-sdk +pragma solidity ^0.8.21; + +import "../../../utils/common/Structs.sol"; +import "./BorshUtils.sol"; + +library BorshDecoder { + /// Decodes the borsh schema into abi.encode(value) list of params + /// Handles decoding of: + /// 1. u8/u16/u32/u64 Rust types + /// 2. "String" Rust type + /// 3. array/Vec and String numeric Rust types (mentioned in 1) and 2)) + function decodeGenericSchema( + GenericSchema memory schema, + bytes memory encodedData + ) internal pure returns (bytes[] memory) { + bytes[] memory decodedParams = new bytes[](schema.valuesTypeNames.length); + Data memory data = from(encodedData); + + for (uint256 i = 0; i < schema.valuesTypeNames.length; i++) { + string memory typeName = schema.valuesTypeNames[i]; + + if (keccak256(bytes(typeName)) == keccak256(bytes("u8"))) { + uint8 value = data.decodeU8(); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u16"))) { + uint16 value = data.decodeU16(); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u32"))) { + uint32 value = data.decodeU32(); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u64"))) { + uint64 value = data.decodeU64(); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u128"))) { + uint128 value = data.decodeU128(); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("String"))) { + string memory value = data.decodeString(); + decodedParams[i] = abi.encode(value); + } + // Handle Vector types with variable length + else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint32 length; + uint8[] memory value; + (length, value) = decodeUint8Vec(data); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint32 length; + uint16[] memory value; + (length, value) = decodeUint16Vec(data); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint32 length; + uint32[] memory value; + (length, value) = decodeUint32Vec(data); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint32 length; + uint64[] memory value; + (length, value) = decodeUint64Vec(data); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint32 length; + uint128[] memory value; + (length, value) = decodeUint128Vec(data); + decodedParams[i] = abi.encode(value); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint32 length; + string[] memory value; + (length, value) = decodeStringVec(data); + decodedParams[i] = abi.encode(value); + } + // Handle Array types with fixed length + else if (BorshUtils.startsWith(typeName, "[u8;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); + uint8[] memory value = decodeUint8Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (BorshUtils.startsWith(typeName, "[u16;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); + uint16[] memory value = decodeUint16Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (BorshUtils.startsWith(typeName, "[u32;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); + uint32[] memory value = decodeUint32Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (BorshUtils.startsWith(typeName, "[u64;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); + uint64[] memory value = decodeUint64Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (BorshUtils.startsWith(typeName, "[u128;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); + uint128[] memory value = decodeUint128Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (BorshUtils.startsWith(typeName, "[String;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); + string[] memory value = decodeStringArray(data, length); + decodedParams[i] = abi.encode(value); + } else { + revert("Unsupported type"); + } + } + + return decodedParams; + } + + /********* Decode primitive types *********/ + + using BorshDecoder for Data; + + struct Data { + uint256 ptr; + uint256 end; + } + + /********* Helper to manage data pointer *********/ + + function from(bytes memory data) internal pure returns (Data memory res) { + uint256 ptr; + assembly { + ptr := data + } + unchecked { + res.ptr = ptr + 32; + res.end = res.ptr + BorshUtils.readMemory(ptr); + } + } + + // This function assumes that length is reasonably small, so that data.ptr + length will not overflow. In the current code, length is always less than 2^32. + function requireSpace(Data memory data, uint256 length) internal pure { + unchecked { + require(data.ptr + length <= data.end, "Parse error: unexpected EOI"); + } + } + + function read(Data memory data, uint256 length) internal pure returns (bytes32 res) { + data.requireSpace(length); + res = bytes32(BorshUtils.readMemory(data.ptr)); + unchecked { + data.ptr += length; + } + return res; + } + + function done(Data memory data) internal pure { + require(data.ptr == data.end, "Parse error: EOI expected"); + } + + /********* Decoders for primitive types *********/ + + function decodeU8(Data memory data) internal pure returns (uint8) { + return uint8(bytes1(data.read(1))); + } + + function decodeU16(Data memory data) internal pure returns (uint16) { + return BorshUtils.swapBytes2(uint16(bytes2(data.read(2)))); + } + + function decodeU32(Data memory data) internal pure returns (uint32) { + return BorshUtils.swapBytes4(uint32(bytes4(data.read(4)))); + } + + function decodeU64(Data memory data) internal pure returns (uint64) { + return BorshUtils.swapBytes8(uint64(bytes8(data.read(8)))); + } + + function decodeU128(Data memory data) internal pure returns (uint128) { + return BorshUtils.swapBytes16(uint128(bytes16(data.read(16)))); + } + + function decodeU256(Data memory data) internal pure returns (uint256) { + return BorshUtils.swapBytes32(uint256(data.read(32))); + } + + function decodeBytes20(Data memory data) internal pure returns (bytes20) { + return bytes20(data.read(20)); + } + + function decodeBytes32(Data memory data) internal pure returns (bytes32) { + return data.read(32); + } + + function decodeBool(Data memory data) internal pure returns (bool) { + uint8 res = data.decodeU8(); + require(res <= 1, "Parse error: invalid bool"); + return res != 0; + } + + function skipBytes(Data memory data) internal pure { + uint256 length = data.decodeU32(); + data.requireSpace(length); + unchecked { + data.ptr += length; + } + } + + function decodeBytes(Data memory data) internal pure returns (bytes memory res) { + uint256 length = data.decodeU32(); + data.requireSpace(length); + res = BorshUtils.memoryToBytes(data.ptr, length); + unchecked { + data.ptr += length; + } + } + + function decodeString(Data memory data) internal pure returns (string memory) { + bytes memory stringBytes = data.decodeBytes(); + return string(stringBytes); + } + + /********* Decode Vector types with variable length *********/ + + function decodeUint8Vec(Data memory data) internal pure returns (uint32, uint8[] memory) { + uint32 length = data.decodeU32(); + uint8[] memory values = new uint8[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU8(); + } + + return (length, values); + } + + function decodeUint16Vec(Data memory data) internal pure returns (uint32, uint16[] memory) { + uint32 length = data.decodeU32(); + uint16[] memory values = new uint16[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU16(); + } + + return (length, values); + } + + function decodeUint32Vec(Data memory data) internal pure returns (uint32, uint32[] memory) { + uint32 length = data.decodeU32(); + uint32[] memory values = new uint32[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU32(); + } + + return (length, values); + } + + function decodeUint64Vec(Data memory data) internal pure returns (uint32, uint64[] memory) { + uint32 length = data.decodeU32(); + uint64[] memory values = new uint64[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU64(); + } + + return (length, values); + } + + function decodeUint128Vec(Data memory data) internal pure returns (uint32, uint128[] memory) { + uint32 length = data.decodeU32(); + uint128[] memory values = new uint128[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU128(); + } + + return (length, values); + } + + function decodeStringVec(Data memory data) internal pure returns (uint32, string[] memory) { + uint32 length = data.decodeU32(); + string[] memory values = new string[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeString(); + } + + return (length, values); + } + + /********* Decode array types with fixed length *********/ + + function decodeUint8Array( + Data memory data, + uint256 length + ) internal pure returns (uint8[] memory) { + uint8[] memory values = new uint8[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU8(); + } + + return values; + } + + function decodeUint16Array( + Data memory data, + uint256 length + ) internal pure returns (uint16[] memory) { + uint16[] memory values = new uint16[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU16(); + } + + return values; + } + + function decodeUint32Array( + Data memory data, + uint256 length + ) internal pure returns (uint32[] memory) { + uint32[] memory values = new uint32[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU32(); + } + + return values; + } + + function decodeUint64Array( + Data memory data, + uint256 length + ) internal pure returns (uint64[] memory) { + uint64[] memory values = new uint64[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU64(); + } + + return values; + } + + function decodeUint128Array( + Data memory data, + uint256 length + ) internal pure returns (uint128[] memory) { + uint128[] memory values = new uint128[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeU128(); + } + + return values; + } + + function decodeStringArray( + Data memory data, + uint256 length + ) internal pure returns (string[] memory) { + string[] memory values = new string[](length); + + for (uint256 i = 0; i < length; i++) { + values[i] = data.decodeString(); + } + + return values; + } +} \ No newline at end of file diff --git a/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol b/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol new file mode 100644 index 00000000..96240761 --- /dev/null +++ b/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol @@ -0,0 +1,317 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Based on Aurora bridge repo: https://github.com/aurora-is-near/aurora-contracts-sdk/blob/main/aurora-solidity-sdk +pragma solidity ^0.8.21; + +import "../../../utils/common/Structs.sol"; +import "./BorshUtils.sol"; + +library BorshEncoder { + function encodeFunctionArgs( + SolanaInstruction memory instruction + ) internal pure returns (bytes memory) { + bytes memory functionArgsPacked; + for (uint256 i = 0; i < instruction.data.functionArguments.length; i++) { + string memory typeName = instruction.description.functionArgumentTypeNames[i]; + bytes memory data = instruction.data.functionArguments[i]; + + if (keccak256(bytes(typeName)) == keccak256(bytes("u8"))) { + uint256 abiDecodedArg = abi.decode(data, (uint256)); + uint8 arg = uint8(abiDecodedArg); + bytes1 borshEncodedArg = encodeU8(arg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u16"))) { + uint256 abiDecodedArg = abi.decode(data, (uint256)); + uint16 arg = uint16(abiDecodedArg); + bytes2 borshEncodedArg = encodeU16(arg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u32"))) { + uint256 abiDecodedArg = abi.decode(data, (uint256)); + uint32 arg = uint32(abiDecodedArg); + bytes4 borshEncodedArg = encodeU32(arg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u64"))) { + uint256 abiDecodedArg = abi.decode(data, (uint256)); + uint64 arg = uint64(abiDecodedArg); + bytes8 borshEncodedArg = encodeU64(arg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u128"))) { + uint256 abiDecodedArg = abi.decode(data, (uint256)); + uint128 arg = uint128(abiDecodedArg); + bytes16 borshEncodedArg = encodeU128(arg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("String"))) { + string memory abiDecodedArg = abi.decode(data, (string)); + bytes memory borshEncodedArg = encodeString(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } + // Handle array types with fixed length + else if (BorshUtils.startsWith(typeName, "u8[]")) { + uint8[] memory abiDecodedArg = abi.decode(data, (uint8[])); + bytes memory borshEncodedArg = encodeUint8Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u16[]"))) { + uint16[] memory abiDecodedArg = abi.decode(data, (uint16[])); + bytes memory borshEncodedArg = encodeUint16Vec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u32[]"))) { + uint32[] memory abiDecodedArg = abi.decode(data, (uint32[])); + bytes memory borshEncodedArg = encodeUint32Vec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u64[]"))) { + uint64[] memory abiDecodedArg = abi.decode(data, (uint64[])); + bytes memory borshEncodedArg = encodeUint64Vec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u128[]"))) { + uint128[] memory abiDecodedArg = abi.decode(data, (uint128[])); + bytes memory borshEncodedArg = encodeUint128Vec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("String[]"))) { + string[] memory abiDecodedArg = abi.decode(data, (string[])); + bytes memory borshEncodedArg = encodeStringArray(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } + // Handle Vector types with that can have variable length - length prefix is added + else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint8[] memory abiDecodedArg = abi.decode(data, (uint8[])); + bytes memory borshEncodedArg = encodeUint8Vec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint16[] memory abiDecodedArg = abi.decode(data, (uint16[])); + bytes memory borshEncodedArg = encodeUint16Vec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint32[] memory abiDecodedArg = abi.decode(data, (uint32[])); + bytes memory borshEncodedArg = encodeUint32Vec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint64[] memory abiDecodedArg = abi.decode(data, (uint64[])); + bytes memory borshEncodedArg = encodeUint64Vec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + uint128[] memory abiDecodedArg = abi.decode(data, (uint128[])); + bytes memory borshEncodedArg = encodeUint128Vec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("Vec"))) { + string[] memory abiDecodedArg = abi.decode(data, (string[])); + bytes memory borshEncodedArg = encodeStringVec(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } + // Handle array types with fixed length - no length prefix, just the bytes + else if (BorshUtils.startsWith(typeName, "[u8;")) { + // Old code to be fixed: + // uint8[] memory abiDecodedArg = abi.decode(data, (uint8[])); + // bytes memory borshEncodedArg = encodeUint8Array(abiDecodedArg); + // functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + + // TODO: this is a tmp fix, later we must fix this to use GenericSchema for decoding the arrays + // like in test: BorshEncoderTest.testEncodeBytes32Account() + + uint256 expectedLength = BorshUtils.extractArrayLength(typeName); + if (data.length == expectedLength) { + // payload already provided as tightly packed bytes + functionArgsPacked = abi.encodePacked(functionArgsPacked, data); + } else if (data.length == expectedLength * 32) { + // payload encoded as abi.encode(uint8[N]) -> each element padded to 32 bytes + uint8[] memory abiDecodedArg = new uint8[](expectedLength); + for (uint256 j = 0; j < expectedLength; j++) { + abiDecodedArg[j] = uint8(data[j * 32 + 31]); + } + bytes memory borshEncodedArg = encodeUint8Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else { + uint8[] memory abiDecodedArg = abi.decode(data, (uint8[])); + require(abiDecodedArg.length == expectedLength, "[u8;N] length mismatch"); + bytes memory borshEncodedArg = encodeUint8Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } + } else if (BorshUtils.startsWith(typeName, "[u16;")) { + uint16[] memory abiDecodedArg = abi.decode(data, (uint16[])); + bytes memory borshEncodedArg = encodeUint16Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (BorshUtils.startsWith(typeName, "[u32;")) { + uint32[] memory abiDecodedArg = abi.decode(data, (uint32[])); + bytes memory borshEncodedArg = encodeUint32Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (BorshUtils.startsWith(typeName, "[u64;")) { + uint64[] memory abiDecodedArg = abi.decode(data, (uint64[])); + bytes memory borshEncodedArg = encodeUint64Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (BorshUtils.startsWith(typeName, "[u128;")) { + uint128[] memory abiDecodedArg = abi.decode(data, (uint128[])); + bytes memory borshEncodedArg = encodeUint128Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (BorshUtils.startsWith(typeName, "[String;")) { + string[] memory abiDecodedArg = abi.decode(data, (string[])); + bytes memory borshEncodedArg = encodeStringArray(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else { + revert("Unsupported type"); + } + } + return functionArgsPacked; + } + + /********* Encode functions *********/ + + /** Encode primitive types **/ + + function encodeU8(uint8 v) internal pure returns (bytes1) { + return bytes1(v); + } + + function encodeU16(uint16 v) internal pure returns (bytes2) { + return bytes2(BorshUtils.swapBytes2(v)); + } + + function encodeU32(uint32 v) internal pure returns (bytes4) { + return bytes4(BorshUtils.swapBytes4(v)); + } + + function encodeU64(uint64 v) internal pure returns (bytes8) { + return bytes8(BorshUtils.swapBytes8(v)); + } + + function encodeU128(uint128 v) internal pure returns (bytes16) { + return bytes16(BorshUtils.swapBytes16(v)); + } + + /// Encode bytes vector into borsh. Use this method to encode strings as well. + function encodeBytes(bytes memory value) internal pure returns (bytes memory) { + require(value.length <= type(uint32).max, "vector length overflow (must fit in uint32)"); + return abi.encodePacked(encodeU32(uint32(value.length)), bytes(value)); + } + + function encodeString(string memory value) internal pure returns (bytes memory) { + bytes memory strBytes = bytes(value); + return bytes.concat(encodeU32(uint32(strBytes.length)), strBytes); + } + + /** Encode Vector types with that can have variable length **/ + + function encodeUint8Vec(uint8[] memory arr) internal pure returns (bytes memory) { + require(arr.length <= type(uint32).max, "vector length overflow (must fit in uint32)"); + bytes memory packed = packUint8Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeUint16Vec(uint16[] memory arr) internal pure returns (bytes memory) { + require(arr.length <= type(uint32).max, "vector length overflow (must fit in uint32)"); + bytes memory packed = packUint16Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeUint32Vec(uint32[] memory arr) internal pure returns (bytes memory) { + require(arr.length <= type(uint32).max, "vector length overflow (must fit in uint32)"); + bytes memory packed = packUint32Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeUint64Vec(uint64[] memory arr) internal pure returns (bytes memory) { + require(arr.length <= type(uint32).max, "vector length overflow (must fit in uint32)"); + bytes memory packed = packUint64Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeUint128Vec(uint128[] memory arr) internal pure returns (bytes memory) { + require(arr.length <= type(uint32).max, "vector length overflow (must fit in uint32)"); + bytes memory packed = packUint128Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeStringVec(string[] memory arr) internal pure returns (bytes memory) { + require(arr.length <= type(uint32).max, "string length overflow (must fit in uint32)"); + bytes memory packed = packStringArray(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + /** Encode array types with fixed length - no length prefix, just the bytes **/ + + function encodeUint8Array(uint8[] memory arr) internal pure returns (bytes memory) { + return packUint8Array(arr); + } + + function encodeUint16Array(uint16[] memory arr) internal pure returns (bytes memory) { + return packUint16Array(arr); + } + + function encodeUint32Array(uint32[] memory arr) internal pure returns (bytes memory) { + return packUint32Array(arr); + } + + function encodeUint64Array(uint64[] memory arr) internal pure returns (bytes memory) { + return packUint64Array(arr); + } + + function encodeUint128Array(uint128[] memory arr) internal pure returns (bytes memory) { + return packUint128Array(arr); + } + + function encodeStringArray(string[] memory arr) internal pure returns (bytes memory) { + return packStringArray(arr); + } + + /********* Packing functions *********/ + + // NOTE: + // When you use abi.encodePacked() on a dynamic array (uint8[]), Solidity applies ABI encoding rules where each array element gets padded to 32 bytes: + // this is why when you have: + //uint8[] memory value = new uint8[](3); + // value[0] = 1; + // value[1] = 2; + // value[2] = 3; + // bytes memory encoded = abi.encodePacked(value); + // console.logBytes(encoded); + // you get: + // 0x000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003 + // cause each element is padded to 32 bytes + + // --> Below function packs the array into elements without the padding + + function packUint8Array(uint8[] memory arr) internal pure returns (bytes memory) { + bytes memory out; + for (uint i = 0; i < arr.length; i++) { + out = bytes.concat(out, encodeU8(arr[i])); + } + return out; + } + + function packUint16Array(uint16[] memory arr) internal pure returns (bytes memory) { + bytes memory out; + for (uint256 i = 0; i < arr.length; i++) { + out = bytes.concat(out, encodeU16(arr[i])); + } + return out; + } + + function packUint32Array(uint32[] memory arr) internal pure returns (bytes memory) { + bytes memory out; + for (uint256 i = 0; i < arr.length; i++) { + out = bytes.concat(out, encodeU32(arr[i])); + } + return out; + } + + function packUint64Array(uint64[] memory arr) internal pure returns (bytes memory) { + bytes memory out; + for (uint256 i = 0; i < arr.length; i++) { + out = bytes.concat(out, encodeU64(arr[i])); + } + return out; + } + + function packUint128Array(uint128[] memory arr) internal pure returns (bytes memory) { + bytes memory out; + for (uint256 i = 0; i < arr.length; i++) { + out = bytes.concat(out, encodeU128(arr[i])); + } + return out; + } + + function packStringArray(string[] memory arr) internal pure returns (bytes memory) { + bytes memory out; + for (uint256 i = 0; i < arr.length; i++) { + out = bytes.concat(out, encodeString(arr[i])); + } + return out; + } +} \ No newline at end of file diff --git a/contracts/evmx/watcher/borsh-serde/BorshUtils.sol b/contracts/evmx/watcher/borsh-serde/BorshUtils.sol new file mode 100644 index 00000000..0bcd31c1 --- /dev/null +++ b/contracts/evmx/watcher/borsh-serde/BorshUtils.sol @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-3.0-only +// Based on Aurora bridge repo: https://github.com/aurora-is-near/aurora-contracts-sdk/blob/main/aurora-solidity-sdk +pragma solidity ^0.8.21; + +library BorshUtils { + function readMemory(uint256 ptr) internal pure returns (uint256 res) { + assembly { + res := mload(ptr) + } + } + + function writeMemory(uint256 ptr, uint256 value) internal pure { + assembly { + mstore(ptr, value) + } + } + + function memoryToBytes(uint256 ptr, uint256 length) internal pure returns (bytes memory res) { + if (length != 0) { + assembly { + // 0x40 is the address of free memory pointer. + res := mload(0x40) + let end := add( + res, + and( + add(length, 63), + 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0 + ) + ) + // end = res + 32 + 32 * ceil(length / 32). + mstore(0x40, end) + mstore(res, length) + let destPtr := add(res, 32) + // prettier-ignore + for {} 1 {} { + mstore(destPtr, mload(ptr)) + destPtr := add(destPtr, 32) + if eq(destPtr, end) { break } + ptr := add(ptr, 32) + } + } + } + } + + function swapBytes2(uint16 v) internal pure returns (uint16) { + return (v << 8) | (v >> 8); + } + + function swapBytes4(uint32 v) internal pure returns (uint32) { + v = ((v & 0x00ff00ff) << 8) | ((v & 0xff00ff00) >> 8); + return (v << 16) | (v >> 16); + } + + function swapBytes8(uint64 v) internal pure returns (uint64) { + v = ((v & 0x00ff00ff00ff00ff) << 8) | ((v & 0xff00ff00ff00ff00) >> 8); + v = ((v & 0x0000ffff0000ffff) << 16) | ((v & 0xffff0000ffff0000) >> 16); + return (v << 32) | (v >> 32); + } + + function swapBytes16(uint128 v) internal pure returns (uint128) { + v = + ((v & 0x00ff00ff00ff00ff00ff00ff00ff00ff) << 8) | + ((v & 0xff00ff00ff00ff00ff00ff00ff00ff00) >> 8); + v = + ((v & 0x0000ffff0000ffff0000ffff0000ffff) << 16) | + ((v & 0xffff0000ffff0000ffff0000ffff0000) >> 16); + v = + ((v & 0x00000000ffffffff00000000ffffffff) << 32) | + ((v & 0xffffffff00000000ffffffff00000000) >> 32); + return (v << 64) | (v >> 64); + } + + function swapBytes32(uint256 v) internal pure returns (uint256) { + v = + ((v & 0x00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff) << 8) | + ((v & 0xff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00ff00) >> 8); + v = + ((v & 0x0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff) << 16) | + ((v & 0xffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000) >> 16); + v = + ((v & 0x00000000ffffffff00000000ffffffff00000000ffffffff00000000ffffffff) << 32) | + ((v & 0xffffffff00000000ffffffff00000000ffffffff00000000ffffffff00000000) >> 32); + v = + ((v & 0x0000000000000000ffffffffffffffff0000000000000000ffffffffffffffff) << 64) | + ((v & 0xffffffffffffffff0000000000000000ffffffffffffffff0000000000000000) >> 64); + return (v << 128) | (v >> 128); + } + + function startsWith(string memory str, string memory prefix) internal pure returns (bool) { + bytes memory strBytes = bytes(str); + bytes memory prefixBytes = bytes(prefix); + + if (prefixBytes.length > strBytes.length) return false; + + for (uint256 i = 0; i < prefixBytes.length; i++) { + if (strBytes[i] != prefixBytes[i]) return false; + } + return true; + } + + function extractArrayLength(string memory typeName) internal pure returns (uint256) { + bytes memory typeBytes = bytes(typeName); + uint256 length = 0; + bool foundSemicolon = false; + bool foundDigit = false; + + // Parse patterns like "[u8; 32]" + for (uint256 i = 0; i < typeBytes.length; i++) { + bytes1 char = typeBytes[i]; + + if (char == 0x3B) { + // ';' + foundSemicolon = true; + } else if (foundSemicolon && char >= 0x30 && char <= 0x39) { + // '0' to '9' + foundDigit = true; + length = length * 10 + uint256(uint8(char)) - 48; // Convert ASCII to number + } else if (foundSemicolon && foundDigit && char == 0x5D) { + // ']' + break; // End of array type declaration + } else if (foundSemicolon && foundDigit && char != 0x20) { + // Not a space + // If we found digits but hit a non-digit non-space, invalid format + revert("Invalid array length format"); + } + // Skip spaces and other characters before semicolon + } + + require(foundSemicolon && foundDigit && length > 0, "Could not extract array length"); + return length; + } +} \ No newline at end of file diff --git a/contracts/evmx/watcher/precompiles/ReadPrecompile.sol b/contracts/evmx/watcher/precompiles/ReadPrecompile.sol index 4dd757ad..8050e9a7 100644 --- a/contracts/evmx/watcher/precompiles/ReadPrecompile.sol +++ b/contracts/evmx/watcher/precompiles/ReadPrecompile.sol @@ -65,7 +65,7 @@ contract ReadPrecompile is IPrecompile { } function resolvePayload(Payload calldata payload) external onlyWatcher { - if (block.timestamp > payloadParams_.deadline) revert DeadlinePassed(); + if (block.timestamp > payload.deadline) revert DeadlinePassed(); } function setFees(uint256 readFees_) external onlyWatcher { diff --git a/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol b/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol index 2e82b20a..13acc91f 100644 --- a/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/SchedulePrecompile.sol @@ -27,6 +27,8 @@ contract SchedulePrecompile is IPrecompile { IWatcher public watcher__; + error DeadlinePassed(); + modifier onlyWatcher() { if (msg.sender != address(watcher__)) revert OnlyWatcherAllowed(); _; diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index af15d871..c70662de 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -13,6 +13,7 @@ import {WRITE, PAYLOAD_SIZE_LIMIT, CHAIN_SLUG_SOLANA_MAINNET, CHAIN_SLUG_SOLANA_ import {InvalidIndex, MaxMsgValueLimitExceeded, InvalidPayloadSize, OnlyWatcherAllowed, InvalidTarget} from "../../../utils/common/Errors.sol"; import "../../../utils/RescueFundsLib.sol"; import {toBytes32Format} from "../../../utils/common/Converters.sol"; +import "../borsh-serde/BorshEncoder.sol"; abstract contract WritePrecompileStorage is IPrecompile { // slots [0-49] reserved for gap @@ -65,6 +66,11 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable { event WriteProofUploaded(bytes32 indexed payloadId, bytes proof); event ExpiryTimeSet(uint256 expiryTime); + // TODO: remove after debugging + event SolanaInstructionInput(bytes payload); + event SolanaDecodedInstruction(SolanaInstruction instruction); + event SolanaFunctionArgsPacked(bytes functionArgsPacked); + modifier onlyWatcher() { if ( msg.sender != IWatcherOwner(address(watcher__)).owner() && @@ -85,8 +91,10 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable { ) external reinitializer(1) { writeFees = writeFees_; expiryTime = expiryTime_; - watcher__ = IWatcher(watcher_); _initializeOwner(owner_); + + watcher__ = IWatcher(watcher_); + // _initializeWatcher(watcher_); } function getPrecompileFees(bytes memory) public view returns (uint256) { @@ -120,20 +128,24 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable { fees = getPrecompileFees(precompileData); // create digest - DigestParams memory digestParams_ = DigestParams( - watcher__.sockets(rawPayload.transaction.chainSlug), - toBytes32Format(watcher__.transmitter()), - payloadId, - deadline, - rawPayload.overrideParams.callType, - gasLimit, - rawPayload.overrideParams.value, - rawPayload.transaction.payload, - rawPayload.transaction.target, - abi.encode(toBytes32Format(appGateway)), - bytes32(0), - bytes("") - ); + DigestParams memory digestParams_; + if (_isSolanaChainSlug(rawPayload.transaction.chainSlug)) { + digestParams_ = _createSolanaDigestParams( + rawPayload, + payloadId, + appGateway, + deadline, + gasLimit + ); + } else { + digestParams_ = _createEvmDigestParams( + rawPayload, + payloadId, + appGateway, + deadline, + gasLimit + ); + } // Calculate and store digest from payload parameters bytes32 digest = getDigest(digestParams_); @@ -211,6 +223,82 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable { ); } + function _createEvmDigestParams( + RawPayload memory rawPayload_, + bytes32 payloadId_, + address appGateway_, + uint256 deadline_, + uint256 gasLimit_ + ) internal view returns (DigestParams memory) { + return + DigestParams( + watcher__.sockets(rawPayload_.transaction.chainSlug), + toBytes32Format(watcher__.transmitter()), + payloadId_, + deadline_, + rawPayload_.overrideParams.callType, + gasLimit_, + rawPayload_.overrideParams.value, + rawPayload_.transaction.payload, + rawPayload_.transaction.target, + abi.encode(toBytes32Format(appGateway_)), + bytes32(0), + bytes("") + ); + } + + function _createSolanaDigestParams( + RawPayload memory rawPayload_, + bytes32 payloadId_, + address appGateway_, + uint256 deadline_, + uint256 gasLimit_ + ) internal returns (DigestParams memory) { + emit SolanaInstructionInput(rawPayload_.transaction.payload); + SolanaInstruction memory instruction = abi.decode( + rawPayload_.transaction.payload, + (SolanaInstruction) + ); + emit SolanaDecodedInstruction(instruction); + + bytes memory functionArgsPacked = BorshEncoder.encodeFunctionArgs(instruction); + emit SolanaFunctionArgsPacked(functionArgsPacked); + + bytes memory payloadPacked = abi.encodePacked( + instruction.data.programId, + instruction.data.accounts, + instruction.data.instructionDiscriminator, + functionArgsPacked + ); + + // TODO: this is temporary, must be injected from function arguments + // bytes32 of Solana Socket address : 9vFEQ5e3xf4eo17WttfqmXmnqN3gUicrhFGppmmNwyqV + bytes32 hardcodedSocket = 0x84815e8ca2f6dad7e12902c39a51bc72e13c48139b4fb10025d94e7abea2969c; + // bytes32 of Solana transmitter address : pFCBP4bhqdSsrWUVTgqhPsLrfEdChBK17vgFM7TxjxQ + bytes32 transmitterSolana = 0x0c1a5886fe1093df9fc438c296f9f7275b7718b6bc0e156d8d336c58f083996d; + return + DigestParams( + // watcherPrecompileConfig__.sockets(params_.payloadHeader.getChainSlug()), // TODO: this does not work, for some reason it returns 0x000.... address + hardcodedSocket, + // toBytes32Format(transmitter_), + transmitterSolana, + payloadId_, + deadline_, + rawPayload_.overrideParams.callType, + gasLimit_, + rawPayload_.overrideParams.value, + payloadPacked, + rawPayload_.transaction.target, + abi.encode(toBytes32Format(appGateway_)), + bytes32(0), + bytes("") + ); + } + + function _isSolanaChainSlug(uint32 chainSlug_) internal pure returns (bool) { + return chainSlug_ == CHAIN_SLUG_SOLANA_MAINNET || chainSlug_ == CHAIN_SLUG_SOLANA_DEVNET; + } + /// @notice Marks a write request with a proof on digest /// @param payloadId_ The unique identifier of the request /// @param proof_ The watcher's proof diff --git a/contracts/protocol/switchboard/SwitchboardBase.sol b/contracts/protocol/switchboard/SwitchboardBase.sol index 00ecb9c7..6d840f61 100644 --- a/contracts/protocol/switchboard/SwitchboardBase.sol +++ b/contracts/protocol/switchboard/SwitchboardBase.sol @@ -59,6 +59,7 @@ abstract contract SwitchboardBase is ISwitchboard, AccessControl { ) external view returns (address transmitter) { transmitter = transmitterSignature_.length > 0 ? _recoverSigner( + // TODO: use api encode packed keccak256(abi.encode(address(socket__), payloadId_)), transmitterSignature_ ) diff --git a/contracts/utils/common/Constants.sol b/contracts/utils/common/Constants.sol index 2f98d595..39acb8ea 100644 --- a/contracts/utils/common/Constants.sol +++ b/contracts/utils/common/Constants.sol @@ -3,15 +3,10 @@ pragma solidity ^0.8.21; address constant ETH_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); -bytes32 constant FORWARD_CALL = keccak256("FORWARD_CALL"); -bytes32 constant DISTRIBUTE_FEE = keccak256("DISTRIBUTE_FEE"); -bytes32 constant DEPLOY = keccak256("DEPLOY"); - bytes4 constant READ = bytes4(keccak256("READ")); bytes4 constant WRITE = bytes4(keccak256("WRITE")); bytes4 constant SCHEDULE = bytes4(keccak256("SCHEDULE")); -bytes32 constant CALLBACK = keccak256("CALLBACK"); bytes32 constant FAST = keccak256("FAST"); bytes32 constant CCTP = keccak256("CCTP"); diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index 0fc4f04b..b65d7775 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -163,6 +163,21 @@ struct Payload { bytes precompileData; } +struct CCTPExecutionParams { + ExecuteParams executeParams; + bytes32 digest; + bytes proof; + bytes transmitterSignature; + address refundAddress; +} + +struct CCTPBatchParams { + bytes32[] previousPayloadIds; + uint32[] nextBatchRemoteChainSlugs; + bytes[] messages; + bytes[] attestations; +} + struct SolanaInstruction { SolanaInstructionData data; SolanaInstructionDataDescription description; @@ -184,30 +199,50 @@ struct SolanaInstructionDataDescription { string[] functionArgumentTypeNames; } - // payload fee tracking for refunds (native token flow only) - struct PayloadFees { - uint256 nativeFees; - address refundAddress; - bool isRefundEligible; - bool isRefunded; - address plug; - } - - // sponsored payload fee tracking - struct SponsoredPayloadFees { - uint256 maxFees; - address plug; - } - - /** - * @dev Internal struct for decoded overrides - */ - struct MessageOverrides { - uint32 dstChainSlug; - uint256 gasLimit; - uint256 value; - address refundAddress; - uint256 maxFees; - address sponsor; - bool isSponsored; - } +/** Solana read payload - SolanaReadInstruction **/ + +enum SolanaReadSchemaType { + PREDEFINED, + GENERIC +} + +struct SolanaReadRequest { + bytes32 accountToRead; + SolanaReadSchemaType schemaType; + // keccak256("schema-name") + bytes32 predefinedSchemaNameHash; +} + +// this is only used after getting the data from Solana account +struct GenericSchema { + // list of types recognizable by BorshEncoder that we expect to read from Solana account (data model) + string[] valuesTypeNames; +} + +// payload fee tracking for refunds (native token flow only) +struct PayloadFees { + uint256 nativeFees; + address refundAddress; + bool isRefundEligible; + bool isRefunded; + address plug; +} + +// sponsored payload fee tracking +struct SponsoredPayloadFees { + uint256 maxFees; + address plug; +} + +/** + * @dev Internal struct for decoded overrides + */ +struct MessageOverrides { + uint32 dstChainSlug; + uint256 gasLimit; + uint256 value; + address refundAddress; + uint256 maxFees; + address sponsor; + bool isSponsored; +} diff --git a/foundry.toml b/foundry.toml index 69efa587..44258aa0 100644 --- a/foundry.toml +++ b/foundry.toml @@ -5,10 +5,17 @@ out = "out" libs = ["lib"] ffi = true optimizer = true -optimizer_runs = 200 +optimizer_runs = 1 evm_version = 'paris' via_ir = true +[profile.default.optimizer_details] +yul = true + +[profile.default.optimizer_details.yulDetails] +stackAllocation = true +optimizerSteps = "u" + [labels] 0xAaee0de4a720e8733a397a3B57fcE3B306Cc7dAe = "AddressResolver" 0x8f1BE258CF821f11fdCC392DAe314BF0781b2CE4 = "AddressResolverImpl" diff --git a/test/apps/counter/CounterAppGateway.sol b/test/apps/counter/CounterAppGateway.sol index 159fa44a..d79cf9c9 100644 --- a/test/apps/counter/CounterAppGateway.sol +++ b/test/apps/counter/CounterAppGateway.sol @@ -29,7 +29,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { function incrementCounters(address instances_) public async { incremented = false; ICounter(instances_).increase(); - onCompleteData = abi.encodeWithSelector(this.onIncrementComplete.selector); + then(this.onIncrementComplete.selector, bytes("")); } function onIncrementComplete() public { From ff6dfafd986443250369b4ee54cd0d8c37a1ba87 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 7 Nov 2025 01:18:15 +0530 Subject: [PATCH 04/10] fix: tests --- contracts/evmx/watcher/Watcher.sol | 15 +++++++++++---- test/SetupTest.t.sol | 6 +++++- test/apps/Counter.t.sol | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index 11e8efc9..6d61e704 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -34,6 +34,7 @@ contract Watcher is Initializable, Configurations { error PayloadAlreadyCancelled(); error PayloadAlreadySettled(); + error PayloadAlreadySet(); error AppGatewayMismatch(); constructor() { @@ -56,8 +57,8 @@ contract Watcher is Initializable, Configurations { } function addPayloadData(RawPayload calldata rawPayload_, address appGateway_) external { - // todo: check and revert if already has a payload data - + if (payloadData.asyncPromise != address(0)) revert PayloadAlreadySet(); + payloadData = rawPayload_; currentPayloadId = getCurrentPayloadId( payloadData.transaction.chainSlug, @@ -117,7 +118,10 @@ contract Watcher is Initializable, Configurations { function resolvePayload(WatcherMultiCallParams memory params_) external { _validateSignature(address(this), params_.data, params_.nonce, params_.signature); - (PromiseReturnData memory resolvedPromise, uint256 feesUsed) = abi.decode(params_.data, (PromiseReturnData, uint256)); + (PromiseReturnData memory resolvedPromise, uint256 feesUsed) = abi.decode( + params_.data, + (PromiseReturnData, uint256) + ); _resolvePayload(resolvedPromise, feesUsed); } @@ -161,7 +165,10 @@ contract Watcher is Initializable, Configurations { function markRevert(WatcherMultiCallParams memory params_) external { _validateSignature(address(this), params_.data, params_.nonce, params_.signature); - (PromiseReturnData memory resolvedPromise, bool isRevertingOnchain) = abi.decode(params_.data, (PromiseReturnData, bool)); + (PromiseReturnData memory resolvedPromise, bool isRevertingOnchain) = abi.decode( + params_.data, + (PromiseReturnData, bool) + ); _markRevert(resolvedPromise, isRevertingOnchain); } diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 17edeaaa..1270451d 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -24,6 +24,7 @@ import "../contracts/evmx/watcher/precompiles/WritePrecompile.sol"; import "../contracts/evmx/watcher/precompiles/ReadPrecompile.sol"; import "../contracts/evmx/watcher/precompiles/SchedulePrecompile.sol"; +import "../contracts/evmx/helpers/ForwarderSolana.sol"; import "../contracts/evmx/helpers/AddressResolver.sol"; import "../contracts/evmx/helpers/AsyncDeployer.sol"; import "../contracts/evmx/fees/FeesManager.sol"; @@ -247,6 +248,8 @@ contract DeploySetup is SetupStore { proxyFactory = new ERC1967Factory(); feesPool = new FeesPool(watcherEOA); + ForwarderSolana forwarderSolana = new ForwarderSolana(); + // Deploy implementations for upgradeable contracts feesManagerImpl = new FeesManager(); addressResolverImpl = new AddressResolver(); @@ -272,7 +275,8 @@ contract DeploySetup is SetupStore { address(feesPool), watcherEOA, writeFees, - FAST + FAST, + address(forwarderSolana) ) ); feesManager = FeesManager(feesManagerProxy); diff --git a/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index 308b4867..bd05152f 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -85,7 +85,7 @@ contract CounterTest is AppGatewayBaseSetup { executePayload(); bool incremented = counterGateway.incremented(); - assertEq(incremented, false); + assertEq(incremented, true); assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); assertEq(Counter(optCounter).counter(), optCounterBefore + 1); From e1e990be5176541b20de66fbeeea53dd2e0efe8c Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 7 Nov 2025 11:04:13 +0530 Subject: [PATCH 05/10] fix: lint --- contracts/evmx/fees/FeesManager.sol | 1 - contracts/evmx/fees/MessageResolver.sol | 60 +- .../evmx/helpers/solana-utils/Ed25519.sol | 2 +- .../evmx/helpers/solana-utils/Ed25519_pow.sol | 2 +- .../evmx/helpers/solana-utils/Sha512.sol | 2 +- .../evmx/helpers/solana-utils/SolanaPda.sol | 2 +- .../helpers/solana-utils/SolanaSignature.sol | 2 +- .../solana-utils/program-pda/FeesPlugPdas.sol | 2 +- contracts/evmx/interfaces/IFeesManager.sol | 6 +- .../evmx/watcher/borsh-serde/BorshDecoder.sol | 2 +- .../evmx/watcher/borsh-serde/BorshEncoder.sol | 2 +- .../evmx/watcher/borsh-serde/BorshUtils.sol | 2 +- .../watcher/precompiles/WritePrecompile.sol | 4 +- contracts/protocol/Socket.sol | 22 +- contracts/protocol/SocketConfig.sol | 20 +- .../protocol/interfaces/ISwitchboard.sol | 12 +- .../protocol/switchboard/FastSwitchboard.sol | 19 +- .../switchboard/MessageSwitchboard.sol | 86 +-- .../protocol/switchboard/SwitchboardBase.sol | 2 +- hardhat-scripts/deploy/6.connect.ts | 10 +- script/counter/IncrementCountersFromApp.s.sol | 1 - test/SetupTest.t.sol | 14 +- test/Utils.t.sol | 5 +- test/mocks/MockPlug.sol | 26 +- test/switchboard/MessageSwitchboard.t.sol | 644 ++++++++++-------- 25 files changed, 509 insertions(+), 441 deletions(-) diff --git a/contracts/evmx/fees/FeesManager.sol b/contracts/evmx/fees/FeesManager.sol index 79dbc389..a3476d53 100644 --- a/contracts/evmx/fees/FeesManager.sol +++ b/contracts/evmx/fees/FeesManager.sol @@ -72,7 +72,6 @@ contract FeesManager is Credit { susdcSolanaProgramId = susdcSolanaProgramId_; } - function setChainMaxFees( uint32[] calldata chainSlugs_, uint256[] calldata maxFees_ diff --git a/contracts/evmx/fees/MessageResolver.sol b/contracts/evmx/fees/MessageResolver.sol index 86047f92..3c38ba31 100644 --- a/contracts/evmx/fees/MessageResolver.sol +++ b/contracts/evmx/fees/MessageResolver.sol @@ -50,9 +50,9 @@ abstract contract MessageResolverStorage { // Execution status enum enum ExecutionStatus { - NotAdded, // Message not yet added - Pending, // Message added, awaiting execution - Executed // Payment completed + NotAdded, // Message not yet added + Pending, // Message added, awaiting execution + Executed // Payment completed } // slot 51 @@ -76,36 +76,41 @@ abstract contract MessageResolverStorage { * @dev Uses Credits (ERC20) from FeesManager for payment settlement * @dev Upgradeable proxy pattern with AddressResolverUtil */ -contract MessageResolver is MessageResolverStorage, Initializable, AccessControl, AddressResolverUtil { +contract MessageResolver is + MessageResolverStorage, + Initializable, + AccessControl, + AddressResolverUtil +{ //////////////////////////////////////////////////////// ////////////////////// ERRORS ////////////////////////// //////////////////////////////////////////////////////// - + /// @notice Thrown when watcher is not authorized error UnauthorizedWatcher(); - + /// @notice Thrown when nonce has already been used error NonceAlreadyUsed(); - + /// @notice Thrown when message is already added error MessageAlreadyExists(); - + /// @notice Thrown when message is not found error MessageNotFound(); - + /// @notice Thrown when message is not in pending status error MessageNotPending(); - + /// @notice Thrown when payment transfer fails error PaymentFailed(); - + /// @notice Thrown when sponsor has insufficient credits error InsufficientSponsorCredits(); //////////////////////////////////////////////////////// ////////////////////// EVENTS ////////////////////////// //////////////////////////////////////////////////////// - + /// @notice Emitted when message details are added event MessageDetailsAdded( bytes32 indexed payloadId, @@ -118,7 +123,7 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl uint256 feeAmount, uint256 deadline ); - + /// @notice Emitted when transmitter is paid event TransmitterPaid( bytes32 indexed payloadId, @@ -126,14 +131,14 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl address indexed transmitter, uint256 feeAmount ); - + /// @notice Emitted when message is marked as executed by watcher event MessageMarkedExecuted(bytes32 indexed payloadId, address indexed watcher); //////////////////////////////////////////////////////// ////////////////////// CONSTRUCTOR ///////////////////// //////////////////////////////////////////////////////// - + constructor() { _disableInitializers(); // disable for implementation } @@ -236,11 +241,7 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl * @param signature_ Watcher signature confirming execution * @param nonce_ Nonce to prevent replay attacks */ - function markExecuted( - bytes32 payloadId_, - uint256 nonce_, - bytes calldata signature_ - ) external { + function markExecuted(bytes32 payloadId_, uint256 nonce_, bytes calldata signature_) external { MessageDetails storage details = messageDetails[payloadId_]; // Verify message exists @@ -251,12 +252,7 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl // Create digest for signature verification bytes32 digest = keccak256( - abi.encodePacked( - toBytes32Format(address(this)), - evmxChainSlug, - payloadId_, - nonce_ - ) + abi.encodePacked(toBytes32Format(address(this)), evmxChainSlug, payloadId_, nonce_) ); // Recover signer from signature @@ -286,12 +282,7 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl if (!success) revert PaymentFailed(); emit MessageMarkedExecuted(payloadId_, watcher); - emit TransmitterPaid( - payloadId_, - details.sponsor, - details.transmitter, - details.feeAmount - ); + emit TransmitterPaid(payloadId_, details.sponsor, details.transmitter, details.feeAmount); } //////////////////////////////////////////////////////// @@ -323,9 +314,7 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl * @param payloadId_ Unique identifier for the payload * @return Message details struct */ - function getMessageDetails( - bytes32 payloadId_ - ) external view returns (MessageDetails memory) { + function getMessageDetails(bytes32 payloadId_) external view returns (MessageDetails memory) { return messageDetails[payloadId_]; } @@ -369,4 +358,3 @@ contract MessageResolver is MessageResolverStorage, Initializable, AccessControl return messageDetails[payloadId_].status; } } - diff --git a/contracts/evmx/helpers/solana-utils/Ed25519.sol b/contracts/evmx/helpers/solana-utils/Ed25519.sol index e4420b3d..9c81addc 100644 --- a/contracts/evmx/helpers/solana-utils/Ed25519.sol +++ b/contracts/evmx/helpers/solana-utils/Ed25519.sol @@ -903,4 +903,4 @@ library Ed25519 { return vr == r; } } -} \ No newline at end of file +} diff --git a/contracts/evmx/helpers/solana-utils/Ed25519_pow.sol b/contracts/evmx/helpers/solana-utils/Ed25519_pow.sol index 3af4725a..681efca6 100644 --- a/contracts/evmx/helpers/solana-utils/Ed25519_pow.sol +++ b/contracts/evmx/helpers/solana-utils/Ed25519_pow.sol @@ -326,4 +326,4 @@ library Ed25519_pow { 0x7fffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffff_ffffffed ); } -} \ No newline at end of file +} diff --git a/contracts/evmx/helpers/solana-utils/Sha512.sol b/contracts/evmx/helpers/solana-utils/Sha512.sol index 7f04221a..fb0776af 100644 --- a/contracts/evmx/helpers/solana-utils/Sha512.sol +++ b/contracts/evmx/helpers/solana-utils/Sha512.sol @@ -275,4 +275,4 @@ library Sha512 { return H; } -} \ No newline at end of file +} diff --git a/contracts/evmx/helpers/solana-utils/SolanaPda.sol b/contracts/evmx/helpers/solana-utils/SolanaPda.sol index 1a276334..741f2b36 100644 --- a/contracts/evmx/helpers/solana-utils/SolanaPda.sol +++ b/contracts/evmx/helpers/solana-utils/SolanaPda.sol @@ -267,4 +267,4 @@ library SolanaPDA { } return true; } -} \ No newline at end of file +} diff --git a/contracts/evmx/helpers/solana-utils/SolanaSignature.sol b/contracts/evmx/helpers/solana-utils/SolanaSignature.sol index c143500b..882f1dc3 100644 --- a/contracts/evmx/helpers/solana-utils/SolanaSignature.sol +++ b/contracts/evmx/helpers/solana-utils/SolanaSignature.sol @@ -14,4 +14,4 @@ contract SolanaSignature { return valid; } -} \ No newline at end of file +} diff --git a/contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol b/contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol index 1480153e..439a3884 100644 --- a/contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol +++ b/contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol @@ -44,4 +44,4 @@ library FeesPlugProgramPda { return SolanaPDA.findProgramAddress(programId, seeds); } -} \ No newline at end of file +} diff --git a/contracts/evmx/interfaces/IFeesManager.sol b/contracts/evmx/interfaces/IFeesManager.sol index 4a96aa5a..fd587cb5 100644 --- a/contracts/evmx/interfaces/IFeesManager.sol +++ b/contracts/evmx/interfaces/IFeesManager.sol @@ -39,9 +39,5 @@ interface IFeesManager { function setMaxFees(uint256 fees_) external; - function transferFrom( - address from_, - address to_, - uint256 amount_ - ) external returns (bool); + function transferFrom(address from_, address to_, uint256 amount_) external returns (bool); } diff --git a/contracts/evmx/watcher/borsh-serde/BorshDecoder.sol b/contracts/evmx/watcher/borsh-serde/BorshDecoder.sol index 1c7671a3..cb22bb57 100644 --- a/contracts/evmx/watcher/borsh-serde/BorshDecoder.sol +++ b/contracts/evmx/watcher/borsh-serde/BorshDecoder.sol @@ -356,4 +356,4 @@ library BorshDecoder { return values; } -} \ No newline at end of file +} diff --git a/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol b/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol index 96240761..131242aa 100644 --- a/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol +++ b/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol @@ -314,4 +314,4 @@ library BorshEncoder { } return out; } -} \ No newline at end of file +} diff --git a/contracts/evmx/watcher/borsh-serde/BorshUtils.sol b/contracts/evmx/watcher/borsh-serde/BorshUtils.sol index 0bcd31c1..82f92ff7 100644 --- a/contracts/evmx/watcher/borsh-serde/BorshUtils.sol +++ b/contracts/evmx/watcher/borsh-serde/BorshUtils.sol @@ -129,4 +129,4 @@ library BorshUtils { require(foundSemicolon && foundDigit && length > 0, "Could not extract array length"); return length; } -} \ No newline at end of file +} diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index c70662de..9a01ff7b 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -92,7 +92,7 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable { writeFees = writeFees_; expiryTime = expiryTime_; _initializeOwner(owner_); - + watcher__ = IWatcher(watcher_); // _initializeWatcher(watcher_); } @@ -260,7 +260,7 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable { (SolanaInstruction) ); emit SolanaDecodedInstruction(instruction); - + bytes memory functionArgsPacked = BorshEncoder.encodeFunctionArgs(instruction); emit SolanaFunctionArgsPacked(functionArgsPacked); diff --git a/contracts/protocol/Socket.sol b/contracts/protocol/Socket.sol index 2bb5147a..a05486f5 100644 --- a/contracts/protocol/Socket.sol +++ b/contracts/protocol/Socket.sol @@ -104,16 +104,15 @@ contract Socket is SocketUtils { ) internal { (, address switchboardAddress) = _verifyPlugSwitchboard(executeParams_.target); // NOTE: the first un-trusted call in the system - address transmitter = ISwitchboard(switchboardAddress) - .getTransmitter(msg.sender, payloadId_, transmitterProof_); + address transmitter = ISwitchboard(switchboardAddress).getTransmitter( + msg.sender, + payloadId_, + transmitterProof_ + ); // create the digest // transmitter, payloadId, appGateway, executeParams_ and there contents are validated using digest verification from switchboard - bytes32 digest = _createDigest( - transmitter, - payloadId_, - executeParams_ - ); + bytes32 digest = _createDigest(transmitter, payloadId_, executeParams_); payloadIdToDigest[payloadId_] = digest; if ( @@ -236,10 +235,7 @@ contract Socket is SocketUtils { * @param payloadId_ The payload ID to increase fees for * @param feesData_ Encoded fees data (type + data) */ - function increaseFeesForPayload( - bytes32 payloadId_, - bytes calldata feesData_ - ) external payable { + function increaseFeesForPayload(bytes32 payloadId_, bytes calldata feesData_) external payable { (, address switchboardAddress) = _verifyPlugSwitchboard(msg.sender); ISwitchboard(switchboardAddress).increaseFeesForPayload{value: msg.value}( payloadId_, @@ -248,7 +244,9 @@ contract Socket is SocketUtils { ); } - function _verifyPlugSwitchboard(address plug_) internal view returns (uint64 switchboardId, address switchboardAddress) { + function _verifyPlugSwitchboard( + address plug_ + ) internal view returns (uint64 switchboardId, address switchboardAddress) { switchboardId = plugSwitchboardIds[plug_]; if (switchboardId == 0) revert PlugNotFound(); if (isValidSwitchboard[switchboardId] != SwitchboardStatus.REGISTERED) diff --git a/contracts/protocol/SocketConfig.sol b/contracts/protocol/SocketConfig.sol index 0401c792..d99fea7b 100644 --- a/contracts/protocol/SocketConfig.sol +++ b/contracts/protocol/SocketConfig.sol @@ -27,7 +27,7 @@ abstract contract SocketConfig is ISocket, AccessControl { // @notice mapping of plug address to switchboard address mapping(address => uint64) public plugSwitchboardIds; - // @notice max copy bytes for socket + // @notice max copy bytes for socket uint16 public maxCopyBytes = 2048; // 2KB // @notice counter for switchboard ids @@ -121,12 +121,17 @@ abstract contract SocketConfig is ISocket, AccessControl { * @param configData_ The configuration data for the switchboard */ function connect(uint64 switchboardId_, bytes memory configData_) external override { - if (switchboardId_ == 0 || isValidSwitchboard[switchboardId_] != SwitchboardStatus.REGISTERED) - revert InvalidSwitchboard(); + if ( + switchboardId_ == 0 || + isValidSwitchboard[switchboardId_] != SwitchboardStatus.REGISTERED + ) revert InvalidSwitchboard(); plugSwitchboardIds[msg.sender] = switchboardId_; if (configData_.length > 0) { - ISwitchboard(switchboardAddresses[switchboardId_]).updatePlugConfig(msg.sender, configData_); + ISwitchboard(switchboardAddresses[switchboardId_]).updatePlugConfig( + msg.sender, + configData_ + ); } emit PlugConnected(msg.sender, switchboardId_, configData_); } @@ -138,7 +143,7 @@ abstract contract SocketConfig is ISocket, AccessControl { function updatePlugConfig(bytes memory configData_) external { uint64 switchboardId = plugSwitchboardIds[msg.sender]; if (switchboardId == 0) revert PlugNotConnected(); - ISwitchboard(switchboardAddresses[switchboardId]).updatePlugConfig(msg.sender,configData_); + ISwitchboard(switchboardAddresses[switchboardId]).updatePlugConfig(msg.sender, configData_); } /** @@ -182,7 +187,10 @@ abstract contract SocketConfig is ISocket, AccessControl { bytes memory extraData_ ) external view returns (uint64 switchboardId, bytes memory configData) { switchboardId = plugSwitchboardIds[plugAddress_]; - configData = ISwitchboard(switchboardAddresses[switchboardId]).getPlugConfig(plugAddress_, extraData_); + configData = ISwitchboard(switchboardAddresses[switchboardId]).getPlugConfig( + plugAddress_, + extraData_ + ); } function getPlugSwitchboard( diff --git a/contracts/protocol/interfaces/ISwitchboard.sol b/contracts/protocol/interfaces/ISwitchboard.sol index 24040195..5ce1dcef 100644 --- a/contracts/protocol/interfaces/ISwitchboard.sol +++ b/contracts/protocol/interfaces/ISwitchboard.sol @@ -15,7 +15,12 @@ interface ISwitchboard { * @param source_ The source of the payload (chainSlug, plug). * @return A boolean indicating whether the payloads is allowed to go through the switchboard or not. */ - function allowPayload(bytes32 digest_, bytes32 payloadId_, address target_, bytes memory source_) external view returns (bool); + function allowPayload( + bytes32 digest_, + bytes32 payloadId_, + address target_, + bytes memory source_ + ) external view returns (bool); /** * @notice Processes a trigger and creates payload @@ -72,5 +77,8 @@ interface ISwitchboard { * @param extraData_ The extra data for the plug * @return configData_ The configuration data for the plug */ - function getPlugConfig(address plug_, bytes memory extraData_) external view returns (bytes memory configData_); + function getPlugConfig( + address plug_, + bytes memory extraData_ + ) external view returns (bytes memory configData_); } diff --git a/contracts/protocol/switchboard/FastSwitchboard.sol b/contracts/protocol/switchboard/FastSwitchboard.sol index cbb12a1c..9a5ebca9 100644 --- a/contracts/protocol/switchboard/FastSwitchboard.sol +++ b/contracts/protocol/switchboard/FastSwitchboard.sol @@ -64,8 +64,13 @@ contract FastSwitchboard is SwitchboardBase { /** * @inheritdoc ISwitchboard */ - function allowPayload(bytes32 digest_, bytes32, address target_, bytes memory source_ ) external view returns (bool) { - (bytes32 appGatewayId) = abi.decode(source_, (bytes32)); + function allowPayload( + bytes32 digest_, + bytes32, + address target_, + bytes memory source_ + ) external view returns (bool) { + bytes32 appGatewayId = abi.decode(source_, (bytes32)); if (plugAppGatewayIds[target_] != appGatewayId) revert InvalidSource(); return isAttested[digest_]; } @@ -79,7 +84,7 @@ contract FastSwitchboard is SwitchboardBase { bytes calldata payload_, bytes calldata overrides_ ) external payable virtual {} - + /** * @inheritdoc ISwitchboard */ @@ -93,7 +98,7 @@ contract FastSwitchboard is SwitchboardBase { * @inheritdoc ISwitchboard */ function updatePlugConfig(address plug_, bytes memory configData_) external virtual { - (bytes32 appGatewayId_) = abi.decode(configData_, ( bytes32)); + bytes32 appGatewayId_ = abi.decode(configData_, (bytes32)); plugAppGatewayIds[plug_] = appGatewayId_; emit PlugConfigUpdated(plug_, appGatewayId_); } @@ -101,8 +106,10 @@ contract FastSwitchboard is SwitchboardBase { /** * @inheritdoc ISwitchboard */ - function getPlugConfig(address plug_, bytes memory extraData_) external view override returns (bytes memory configData_) { + function getPlugConfig( + address plug_, + bytes memory extraData_ + ) external view override returns (bytes memory configData_) { configData_ = abi.encode(plugAppGatewayIds[plug_]); } - } diff --git a/contracts/protocol/switchboard/MessageSwitchboard.sol b/contracts/protocol/switchboard/MessageSwitchboard.sol index 1f18b1a8..d9746527 100644 --- a/contracts/protocol/switchboard/MessageSwitchboard.sol +++ b/contracts/protocol/switchboard/MessageSwitchboard.sol @@ -6,7 +6,7 @@ import {WATCHER_ROLE, FEE_UPDATER_ROLE} from "../../utils/common/AccessRoles.sol import {toBytes32Format} from "../../utils/common/Converters.sol"; import {createPayloadId} from "../../utils/common/IdUtils.sol"; import {DigestParams, MessageOverrides, PayloadFees, SponsoredPayloadFees} from "../../utils/common/Structs.sol"; -import {WRITE } from "../../utils/common/Constants.sol"; +import {WRITE} from "../../utils/common/Constants.sol"; import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol"; /** @@ -32,9 +32,8 @@ contract MessageSwitchboard is SwitchboardBase { // minimum message value fees: chainSlug => minimum fee amount mapping(uint32 => uint256) public minMsgValueFees; - mapping(bytes32 => PayloadFees) public payloadFees; - + // sponsored payload fee tracking mapping(bytes32 => SponsoredPayloadFees) public sponsoredPayloadFees; @@ -112,7 +111,11 @@ contract MessageSwitchboard is SwitchboardBase { // Event emitted when minimum message value fees are set event MinMsgValueFeesSet(uint32 indexed chainSlug, uint256 minFees, address indexed updater); // Event emitted when sponsored fees are increased - event SponsoredFeesIncreased(bytes32 indexed payloadId, uint256 newMaxFees, address indexed plug); + event SponsoredFeesIncreased( + bytes32 indexed payloadId, + uint256 newMaxFees, + address indexed plug + ); /** * @dev Constructor function for the MessageSwitchboard contract @@ -143,8 +146,6 @@ contract MessageSwitchboard is SwitchboardBase { emit SiblingConfigSet(chainSlug_, socket_, switchboard_); } - - /** * @dev Function to process trigger and create payload * @param plug_ Source plug address @@ -162,19 +163,23 @@ contract MessageSwitchboard is SwitchboardBase { _validateSibling(overrides.dstChainSlug, plug_); // Create digest and payload ID (common for both flows) - (DigestParams memory digestParams, bytes32 digest, bytes32 payloadId) = _createDigestAndPayloadId( - overrides.dstChainSlug, - plug_, - overrides.gasLimit, - overrides.value, - triggerId_, - payload_ - ); + ( + DigestParams memory digestParams, + bytes32 digest, + bytes32 payloadId + ) = _createDigestAndPayloadId( + overrides.dstChainSlug, + plug_, + overrides.gasLimit, + overrides.value, + triggerId_, + payload_ + ); if (overrides.isSponsored) { // Sponsored flow - check sponsor approval if (!sponsorApprovals[overrides.sponsor][plug_]) revert PlugNotApprovedBySponsor(); - + // Store sponsored fees sponsoredPayloadFees[payloadId] = SponsoredPayloadFees({ maxFees: overrides.maxFees, @@ -228,13 +233,8 @@ contract MessageSwitchboard is SwitchboardBase { if (version == 1) { // Version 1: Native flow - ( - , - uint32 dstChainSlug, - uint256 gasLimit, - uint256 value, - address refundAddress - ) = abi.decode(overrides_, (uint8, uint32, uint256, uint256, address)); + (, uint32 dstChainSlug, uint256 gasLimit, uint256 value, address refundAddress) = abi + .decode(overrides_, (uint8, uint32, uint256, uint256, address)); return MessageOverrides({ @@ -308,7 +308,7 @@ contract MessageSwitchboard is SwitchboardBase { target: siblingPlugs[dstChainSlug_][plug_], source: abi.encode(chainSlug, toBytes32Format(plug_)), prevBatchDigestHash: triggerId_, - extraData:"0x" + extraData: "0x" }); digest = _createDigest(digestParams); } @@ -522,7 +522,7 @@ contract MessageSwitchboard is SwitchboardBase { ) external payable override onlySocket { // Decode the fees type from feesData uint8 feesType = abi.decode(feesData_, (uint8)); - + if (feesType == 1) { // Native fees increase _increaseNativeFees(payloadId_, plug_, feesData_); @@ -533,7 +533,7 @@ contract MessageSwitchboard is SwitchboardBase { revert InvalidFeesType(); } } - + /** * @dev Internal function to increase native fees */ @@ -543,18 +543,18 @@ contract MessageSwitchboard is SwitchboardBase { bytes calldata feesData_ ) internal { PayloadFees storage fees = payloadFees[payloadId_]; - + // Validation: Only the plug that created this payload can increase fees if (fees.plug != plug_) revert UnauthorizedFeeIncrease(); - + // Update native fees if msg.value is provided if (msg.value > 0) { fees.nativeFees += msg.value; } - + emit FeesIncreased(payloadId_, msg.value, feesData_); } - + /** * @dev Internal function to increase sponsored fees */ @@ -564,22 +564,26 @@ contract MessageSwitchboard is SwitchboardBase { bytes calldata feesData_ ) internal { SponsoredPayloadFees storage fees = sponsoredPayloadFees[payloadId_]; - + // Validation: Only the plug that created this payload can increase fees if (fees.plug != plug_) revert UnauthorizedFeeIncrease(); - + // Decode new maxFees (skip first byte which is feesType) (, uint256 newMaxFees) = abi.decode(feesData_, (uint8, uint256)); fees.maxFees = newMaxFees; - + emit SponsoredFeesIncreased(payloadId_, newMaxFees, plug_); } /** * @inheritdoc ISwitchboard */ - function allowPayload(bytes32 digest_, bytes32, address target_, bytes memory source_ ) external view override returns (bool) { - + function allowPayload( + bytes32 digest_, + bytes32, + address target_, + bytes memory source_ + ) external view override returns (bool) { (uint32 srcChainSlug, bytes32 srcPlug) = abi.decode(source_, (uint32, bytes32)); if (siblingPlugs[srcChainSlug][target_] != srcPlug) revert InvalidSource(); // digest has enough attestations @@ -613,9 +617,12 @@ contract MessageSwitchboard is SwitchboardBase { * @notice Updates plug configuration * @param configData_ The configuration data for the plug */ - function updatePlugConfig(address plug_, bytes memory configData_) external override onlySocket { + function updatePlugConfig( + address plug_, + bytes memory configData_ + ) external override onlySocket { (uint32 chainSlug_, bytes32 siblingPlug_) = abi.decode(configData_, (uint32, bytes32)); - if ( + if ( siblingSockets[chainSlug_] == bytes32(0) || siblingSwitchboards[chainSlug_] == bytes32(0) ) { @@ -629,8 +636,11 @@ contract MessageSwitchboard is SwitchboardBase { /** * @inheritdoc ISwitchboard */ - function getPlugConfig(address plug_, bytes memory extraData_) external view override returns (bytes memory configData_) { - (uint32 chainSlug_) = abi.decode(extraData_, (uint32)); + function getPlugConfig( + address plug_, + bytes memory extraData_ + ) external view override returns (bytes memory configData_) { + uint32 chainSlug_ = abi.decode(extraData_, (uint32)); configData_ = abi.encode(siblingPlugs[chainSlug_][plug_]); } diff --git a/contracts/protocol/switchboard/SwitchboardBase.sol b/contracts/protocol/switchboard/SwitchboardBase.sol index 6d840f61..1f4e3a19 100644 --- a/contracts/protocol/switchboard/SwitchboardBase.sol +++ b/contracts/protocol/switchboard/SwitchboardBase.sol @@ -59,7 +59,7 @@ abstract contract SwitchboardBase is ISwitchboard, AccessControl { ) external view returns (address transmitter) { transmitter = transmitterSignature_.length > 0 ? _recoverSigner( - // TODO: use api encode packed + // TODO: use api encode packed keccak256(abi.encode(address(socket__), payloadId_)), transmitterSignature_ ) diff --git a/hardhat-scripts/deploy/6.connect.ts b/hardhat-scripts/deploy/6.connect.ts index f53ed75c..28e2e491 100644 --- a/hardhat-scripts/deploy/6.connect.ts +++ b/hardhat-scripts/deploy/6.connect.ts @@ -1,7 +1,11 @@ import { ethers, Wallet } from "ethers"; import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; import { chains, CONCURRENCY_LIMIT, EVMX_CHAIN_ID, mode } from "../config"; -import { AppGatewayConfig, DeploymentAddresses, WatcherMultiCallParams } from "../constants"; +import { + AppGatewayConfig, + DeploymentAddresses, + WatcherMultiCallParams, +} from "../constants"; import { checkIfAppGatewayIdExists, getAddresses, @@ -170,7 +174,9 @@ export const updateConfigEVMx = async () => { if (appConfigs.length > 0) { console.log({ appConfigs }); const calldata = ethers.utils.defaultAbiCoder.encode( - ["tuple(tuple(bytes32 appGatewayId,uint64 switchboardId) plugConfig,bytes32 plug,uint32 chainSlug)[]"], + [ + "tuple(tuple(bytes32 appGatewayId,uint64 switchboardId) plugConfig,bytes32 plug,uint32 chainSlug)[]", + ], [appConfigs] ); diff --git a/script/counter/IncrementCountersFromApp.s.sol b/script/counter/IncrementCountersFromApp.s.sol index 4c215a25..2014aa22 100644 --- a/script/counter/IncrementCountersFromApp.s.sol +++ b/script/counter/IncrementCountersFromApp.s.sol @@ -34,6 +34,5 @@ contract IncrementCounters is Script { } else { console.log("Arbitrum Sepolia forwarder not yet deployed"); } - } } diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 1270451d..18c3ce13 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -354,8 +354,6 @@ contract DeploySetup is SetupStore { return createSignature(digest, watcherPrivateKey); } - - function predictAsyncPromiseAddress( address invoker_, address forwarder_ @@ -714,7 +712,10 @@ contract WatcherSetup is FeesSetup { contractAddress: address(watcher), data: abi.encode(promiseReturnData, feesAmount), nonce: watcherNonce, - signature: _createWatcherSignature(address(watcher), abi.encode(promiseReturnData, feesAmount)) + signature: _createWatcherSignature( + address(watcher), + abi.encode(promiseReturnData, feesAmount) + ) }); watcherNonce++; watcher.resolvePayload(params); @@ -728,7 +729,10 @@ contract WatcherSetup is FeesSetup { contractAddress: address(watcher), data: abi.encode(promiseReturnData, isRevertingOnchain_), nonce: watcherNonce, - signature: _createWatcherSignature(address(watcher), abi.encode(promiseReturnData, isRevertingOnchain_)) + signature: _createWatcherSignature( + address(watcher), + abi.encode(promiseReturnData, isRevertingOnchain_) + ) }); watcherNonce++; watcher.markRevert(params); @@ -954,5 +958,3 @@ contract MessageSwitchboardSetup is DeploySetup { optConfig.socket.execute(executeParams, transmissionParams); } } - - diff --git a/test/Utils.t.sol b/test/Utils.t.sol index a6d0c975..bc816cdb 100644 --- a/test/Utils.t.sol +++ b/test/Utils.t.sol @@ -4,9 +4,6 @@ pragma solidity ^0.8.21; import "forge-std/Test.sol"; abstract contract Utils is Test { - - - function createSignature( bytes32 digest_, uint256 privateKey_ @@ -29,4 +26,4 @@ abstract contract Utils is Test { function bytes32ToAddress(bytes32 addrBytes32_) public pure returns (address) { return address(uint160(uint256(addrBytes32_))); } -} \ No newline at end of file +} diff --git a/test/mocks/MockPlug.sol b/test/mocks/MockPlug.sol index 65ca5710..1096e256 100644 --- a/test/mocks/MockPlug.sol +++ b/test/mocks/MockPlug.sol @@ -6,53 +6,49 @@ import "../../contracts/protocol/base/MessagePlugBase.sol"; contract MockPlug is MessagePlugBase { uint32 public chainSlug; bytes32 public triggerId; - - constructor(address socket_, uint64 switchboardId_) MessagePlugBase(socket_, switchboardId_) { - } - + + constructor(address socket_, uint64 switchboardId_) MessagePlugBase(socket_, switchboardId_) {} function setSocket(address socket_) external { _setSocket(socket_); } - + function setChainSlug(uint32 chainSlug_) external { chainSlug = chainSlug_; } - + function setOverrides(bytes memory overrides_) external { _setOverrides(overrides_); } - + function getOverrides() external view returns (bytes memory) { return overrides; } - + function trigger(bytes memory data) external { // Mock trigger function triggerId = keccak256(data); } - + function getTriggerId() external view returns (bytes32) { return triggerId; } - + // New method to trigger Socket's triggerAppGateway function triggerSocket(bytes memory data) external payable returns (bytes32) { return socket__.triggerAppGateway{value: msg.value}(data); } - + // Method to connect to socket - function connectToSocket(address socket_,uint64 switchboardId_) external { + function connectToSocket(address socket_, uint64 switchboardId_) external { _setSocket(socket_); switchboardId = switchboardId_; socket__.connect(switchboardId_, ""); switchboard = socket__.switchboardAddresses(switchboardId_); } - - + // Method to increase fees for payload function increaseFeesForPayload(bytes32 payloadId_, bytes memory feesData_) external payable { socket__.increaseFeesForPayload{value: msg.value}(payloadId_, feesData_); } } - diff --git a/test/switchboard/MessageSwitchboard.t.sol b/test/switchboard/MessageSwitchboard.t.sol index 95dfba87..5dfb2065 100644 --- a/test/switchboard/MessageSwitchboard.t.sol +++ b/test/switchboard/MessageSwitchboard.t.sol @@ -18,23 +18,23 @@ contract MessageSwitchboardTest is Test, Utils { uint32 constant SRC_CHAIN = 1; uint32 constant DST_CHAIN = 2; uint256 constant MIN_FEES = 0.001 ether; - + // Test addresses address owner = address(0x1000); address watcher = address(0x2000); address sponsor = address(0x3000); address refundAddress = address(0x4000); address feeUpdater = address(0x5000); - + // Private keys for signing uint256 watcherPrivateKey = 0x1111111111111111111111111111111111111111111111111111111111111111; - + // Contracts Socket socket; MessageSwitchboard messageSwitchboard; MockPlug srcPlug; MockPlug dstPlug; - + // Events event SiblingConfigSet(uint32 indexed chainSlug, bytes32 socket, bytes32 switchboard); event SiblingRegistered(uint32 chainSlug, address plugAddress, bytes32 siblingPlug); @@ -55,58 +55,69 @@ contract MessageSwitchboardTest is Test, Utils { event Refunded(bytes32 indexed payloadId, address indexed refundAddress, uint256 amount); event FeesIncreased(bytes32 indexed payloadId, uint256 additionalNativeFees, bytes feesData); event MinMsgValueFeesSet(uint32 indexed chainSlug, uint256 minFees, address indexed updater); - event SponsoredFeesIncreased(bytes32 indexed payloadId, uint256 newMaxFees, address indexed plug); + event SponsoredFeesIncreased( + bytes32 indexed payloadId, + uint256 newMaxFees, + address indexed plug + ); event PlugConfigUpdated(address indexed plug, uint32 indexed chainSlug, bytes32 siblingPlug); function setUp() public { // Deploy actual Socket contract socket = new Socket(SRC_CHAIN, owner, "1.0.0"); messageSwitchboard = new MessageSwitchboard(SRC_CHAIN, socket, owner); - + // Setup roles - grant watcher role to the address derived from watcherPrivateKey address actualWatcherAddress = getWatcherAddress(); vm.startPrank(owner); messageSwitchboard.grantRole(WATCHER_ROLE, actualWatcherAddress); messageSwitchboard.grantRole(FEE_UPDATER_ROLE, feeUpdater); - + // Register switchboard on Socket (switchboard calls Socket.registerSwitchboard()) messageSwitchboard.registerSwitchboard(); vm.stopPrank(); uint64 switchboardId = messageSwitchboard.switchboardId(); - + // Socket automatically stores switchboard address, no manual setting needed - + // Now create plugs with the registered switchboard ID srcPlug = new MockPlug(address(socket), switchboardId); dstPlug = new MockPlug(address(socket), switchboardId); } - + // Helper to get watcher address function getWatcherAddress() public pure returns (address) { return vm.addr(0x1111111111111111111111111111111111111111111111111111111111111111); } - + // Helper to create payload ID (matches createPayloadId from IdUtils) function createTestPayloadId( uint256 payloadPointer_, uint64 switchboardId_, uint32 chainSlug_ ) public pure returns (bytes32) { - return bytes32((uint256(chainSlug_) << 224) | (uint256(switchboardId_) << 160) | payloadPointer_); + return + bytes32( + (uint256(chainSlug_) << 224) | (uint256(switchboardId_) << 160) | payloadPointer_ + ); } - + /** * @dev Calculate triggerId based on Socket's _encodeTriggerId logic * @param socketAddress The socket contract address * @param triggerCounter The current trigger counter value (before increment) * @return triggerId The calculated trigger ID */ - function calculateTriggerId(address socketAddress, uint64 triggerCounter) public pure returns (bytes32) { - uint256 triggerPrefix = (uint256(SRC_CHAIN) << 224) | (uint256(uint160(socketAddress)) << 64); + function calculateTriggerId( + address socketAddress, + uint64 triggerCounter + ) public pure returns (bytes32) { + uint256 triggerPrefix = (uint256(SRC_CHAIN) << 224) | + (uint256(uint160(socketAddress)) << 64); return bytes32(triggerPrefix | triggerCounter); } - + /** * @dev Calculate payloadId based on MessageSwitchboard's _createDigestAndPayloadId logic * @param triggerId The trigger ID from socket @@ -114,50 +125,54 @@ contract MessageSwitchboardTest is Test, Utils { * @param dstChainSlug The destination chain slug * @return payloadId The calculated payload ID */ - function calculatePayloadId(bytes32 triggerId, uint40 payloadCounter, uint32 dstChainSlug) public view returns (bytes32) { + function calculatePayloadId( + bytes32 triggerId, + uint40 payloadCounter, + uint32 dstChainSlug + ) public view returns (bytes32) { uint160 payloadPointer = (uint160(SRC_CHAIN) << 120) | (uint160(uint64(uint256(triggerId))) << 80) | payloadCounter; - - return createTestPayloadId(payloadPointer, messageSwitchboard.switchboardId(), dstChainSlug); + + return + createTestPayloadId(payloadPointer, messageSwitchboard.switchboardId(), dstChainSlug); } - + /** * @dev Calculate digest based on MessageSwitchboard's _createDigest logic * @param digestParams The digest parameters * @return digest The calculated digest */ function calculateDigest(DigestParams memory digestParams) public pure returns (bytes32) { - return keccak256( - abi.encodePacked( - digestParams.socket, - digestParams.transmitter, - digestParams.payloadId, - digestParams.deadline, - digestParams.callType, - digestParams.gasLimit, - digestParams.value, - digestParams.payload, - digestParams.target, - digestParams.source, - digestParams.prevBatchDigestHash, - digestParams.extraData - ) - ); + return + keccak256( + abi.encodePacked( + digestParams.socket, + digestParams.transmitter, + digestParams.payloadId, + digestParams.deadline, + digestParams.callType, + digestParams.gasLimit, + digestParams.value, + digestParams.payload, + digestParams.target, + digestParams.source, + digestParams.prevBatchDigestHash, + digestParams.extraData + ) + ); } // ============================================ // HELPER FUNCTIONS FOR TEST OPTIMIZATION // ============================================ - + /** * @dev Setup sibling configuration (socket, switchboard, plug registration) */ function _setupSiblingConfig() internal { - _setupSiblingSocketConfig(); _setupSiblingPlugConfig(); - } function _setupSiblingSocketConfig() internal { @@ -167,7 +182,11 @@ contract MessageSwitchboardTest is Test, Utils { vm.startPrank(owner); messageSwitchboard.setSiblingConfig(DST_CHAIN, siblingSocket, siblingSwitchboard); // Also set config for reverse direction - messageSwitchboard.setSiblingConfig(SRC_CHAIN, toBytes32Format(address(socket)), toBytes32Format(address(messageSwitchboard))); + messageSwitchboard.setSiblingConfig( + SRC_CHAIN, + toBytes32Format(address(socket)), + toBytes32Format(address(messageSwitchboard)) + ); vm.stopPrank(); } @@ -176,7 +195,7 @@ contract MessageSwitchboardTest is Test, Utils { srcPlug.registerSibling(DST_CHAIN, address(dstPlug)); dstPlug.registerSibling(SRC_CHAIN, address(srcPlug)); } - + /** * @dev Setup minimum fees for destination chain */ @@ -184,14 +203,17 @@ contract MessageSwitchboardTest is Test, Utils { vm.prank(owner); messageSwitchboard.setMinMsgValueFeesOwner(DST_CHAIN, MIN_FEES); } - + /** * @dev Create a native payload via Socket's triggerAppGateway * @param payloadData The payload data to encode * @param msgValue The msg.value to send with the transaction * @return payloadId The generated payload ID */ - function _createNativePayload(bytes memory payloadData, uint256 msgValue) internal returns (bytes32 payloadId) { + function _createNativePayload( + bytes memory payloadData, + uint256 msgValue + ) internal returns (bytes32 payloadId) { bytes memory overrides = abi.encode( uint8(1), // version DST_CHAIN, @@ -199,30 +221,33 @@ contract MessageSwitchboardTest is Test, Utils { uint256(0), // value refundAddress // refundAddress ); - + // Set overrides on the plug srcPlug.setOverrides(overrides); - + bytes memory payload = abi.encode(payloadData); - + // Get counters before the call uint64 triggerCounterBefore = socket.triggerCounter(); uint40 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + // Use MockPlug to trigger Socket vm.deal(address(srcPlug), 10 ether); srcPlug.triggerSocket{value: msgValue}(payload); - + return _getLastPayloadId(triggerCounterBefore, payloadCounterBefore); } - + /** * @dev Create a sponsored payload via Socket's triggerAppGateway * @param payloadData The payload data to encode * @param maxFees The maximum fees for the sponsored transaction * @return payloadId The generated payload ID */ - function _createSponsoredPayload(bytes memory payloadData, uint256 maxFees) internal returns (bytes32 payloadId) { + function _createSponsoredPayload( + bytes memory payloadData, + uint256 maxFees + ) internal returns (bytes32 payloadId) { bytes memory overrides = abi.encode( uint8(2), // version DST_CHAIN, @@ -231,22 +256,22 @@ contract MessageSwitchboardTest is Test, Utils { maxFees, // maxFees sponsor // sponsor ); - + // Set overrides on the plug srcPlug.setOverrides(overrides); - + bytes memory payload = abi.encode(payloadData); - + // Get counters before the call uint64 triggerCounterBefore = socket.triggerCounter(); uint40 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + // Use MockPlug to trigger Socket srcPlug.triggerSocket(payload); - + return _getLastPayloadId(triggerCounterBefore, payloadCounterBefore); } - + /** * @dev Create DigestParams for attestation with flexible parameters * @param payloadId The payload ID @@ -258,8 +283,8 @@ contract MessageSwitchboardTest is Test, Utils { * @return digestParams The constructed DigestParams */ function _createDigestParams( - bytes32 payloadId, - bytes32 triggerId, + bytes32 payloadId, + bytes32 triggerId, bytes memory payload, address target_, uint256 gasLimit_, @@ -268,23 +293,24 @@ contract MessageSwitchboardTest is Test, Utils { // Get sibling socket from switchboard (matches what contract uses) bytes32 siblingSocket = messageSwitchboard.siblingSockets(DST_CHAIN); bytes32 siblingPlug = messageSwitchboard.siblingPlugs(DST_CHAIN, address(srcPlug)); - - return DigestParams({ - socket: siblingSocket, - transmitter: bytes32(0), - payloadId: payloadId, - deadline: block.timestamp + 3600, - callType: WRITE, - gasLimit: gasLimit_, - value: value_, - payload: payload, - target: siblingPlug, - source: abi.encode(SRC_CHAIN, toBytes32Format(address(srcPlug))), - prevBatchDigestHash: triggerId, - extraData: abi.encode(SRC_CHAIN, toBytes32Format(address(srcPlug))) - }); + + return + DigestParams({ + socket: siblingSocket, + transmitter: bytes32(0), + payloadId: payloadId, + deadline: block.timestamp + 3600, + callType: WRITE, + gasLimit: gasLimit_, + value: value_, + payload: payload, + target: siblingPlug, + source: abi.encode(SRC_CHAIN, toBytes32Format(address(srcPlug))), + prevBatchDigestHash: triggerId, + extraData: abi.encode(SRC_CHAIN, toBytes32Format(address(srcPlug))) + }); } - + /** * @dev Create DigestParams for attestation (simplified version with defaults) * @param payloadId The payload ID @@ -292,21 +318,28 @@ contract MessageSwitchboardTest is Test, Utils { * @param payload The payload data * @return digestParams The constructed DigestParams */ - function _createDigestParams(bytes32 payloadId, bytes32 triggerId, bytes memory payload) internal view returns (DigestParams memory) { + function _createDigestParams( + bytes32 payloadId, + bytes32 triggerId, + bytes memory payload + ) internal view returns (DigestParams memory) { return _createDigestParams(payloadId, triggerId, payload, address(dstPlug), 100000, 0); } - + /** * @dev Get the last created payload ID by reading counters before trigger * @param triggerCounterBefore The trigger counter before the call * @param payloadCounterBefore The payload counter before the call * @return payloadId The calculated payload ID */ - function _getLastPayloadId(uint64 triggerCounterBefore, uint40 payloadCounterBefore) internal view returns (bytes32) { + function _getLastPayloadId( + uint64 triggerCounterBefore, + uint40 payloadCounterBefore + ) internal view returns (bytes32) { bytes32 triggerId = calculateTriggerId(address(socket), triggerCounterBefore); return calculatePayloadId(triggerId, payloadCounterBefore, DST_CHAIN); } - + /** * @dev Create watcher signature for a given payload ID * @param payloadId The payload ID to sign @@ -314,10 +347,12 @@ contract MessageSwitchboardTest is Test, Utils { */ function _createWatcherSignature(bytes32 payloadId) internal view returns (bytes memory) { // markRefundEligible signs: keccak256(abi.encodePacked(switchboardAddress, chainSlug, payloadId)) - bytes32 digest = keccak256(abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, payloadId)); + bytes32 digest = keccak256( + abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, payloadId) + ); return createSignature(digest, watcherPrivateKey); } - + /** * @dev Approve plug for sponsor */ @@ -325,7 +360,7 @@ contract MessageSwitchboardTest is Test, Utils { vm.prank(sponsor); messageSwitchboard.approvePlug(address(srcPlug)); } - + /** * @dev Complete setup for most tests (sibling config + min fees) */ @@ -333,7 +368,7 @@ contract MessageSwitchboardTest is Test, Utils { _setupSiblingConfig(); _setupMinFees(); } - + /** * @dev Complete setup for sponsored tests (sibling config + sponsor approval) */ @@ -342,31 +377,30 @@ contract MessageSwitchboardTest is Test, Utils { _approvePlugForSponsor(); } - function test_setup_Success() public view { assertTrue(messageSwitchboard.chainSlug() == SRC_CHAIN); assertTrue(messageSwitchboard.switchboardId() > 0); assertTrue(messageSwitchboard.owner() == owner); } - + // ============================================ // CRITICAL TESTS - GROUP 1: Sibling Management // ============================================ - + function test_setSiblingConfig_Success() public { bytes32 siblingSocket = toBytes32Format(address(0x1234)); bytes32 siblingSwitchboard = toBytes32Format(address(0x5678)); - + vm.expectEmit(true, true, true, false); emit SiblingConfigSet(DST_CHAIN, siblingSocket, siblingSwitchboard); - + vm.prank(owner); messageSwitchboard.setSiblingConfig(DST_CHAIN, siblingSocket, siblingSwitchboard); - + assertEq(messageSwitchboard.siblingSockets(DST_CHAIN), siblingSocket); assertEq(messageSwitchboard.siblingSwitchboards(DST_CHAIN), siblingSwitchboard); } - + function test_setSiblingConfig_NotOwner_Reverts() public { vm.prank(address(0x9999)); vm.expectRevert(); @@ -376,57 +410,62 @@ contract MessageSwitchboardTest is Test, Utils { toBytes32Format(address(0x5678)) ); } - - function test_registerSibling_Success() public { - + function test_registerSibling_Success() public { _setupSiblingConfig(); vm.expectEmit(true, true, true, false); emit PlugConfigUpdated(address(srcPlug), DST_CHAIN, toBytes32Format(address(dstPlug))); srcPlug.registerSibling(DST_CHAIN, address(dstPlug)); - - (bytes memory configData) = messageSwitchboard.getPlugConfig(address(srcPlug), abi.encode(DST_CHAIN)); - (bytes32 siblingPlug) = abi.decode(configData, (bytes32)); + + bytes memory configData = messageSwitchboard.getPlugConfig( + address(srcPlug), + abi.encode(DST_CHAIN) + ); + bytes32 siblingPlug = abi.decode(configData, (bytes32)); assertEq(siblingPlug, toBytes32Format(address(dstPlug))); } - + function test_registerSibling_SiblingSocketNotFound_Reverts() public { _setupSiblingConfig(); vm.expectRevert(MessageSwitchboard.SiblingSocketNotFound.selector); srcPlug.registerSibling(999, address(0x9999)); } - + // ============================================ // CRITICAL TESTS - GROUP 2: processTrigger - Native Flow // ============================================ - + function test_processTrigger_Native_Success() public { // Setup sibling config _setupCompleteNative(); - + // Prepare overrides for version 1 (Native) bytes memory overrides = abi.encode( - uint8(1), // version + uint8(1), // version DST_CHAIN, - uint256(100000), // gasLimit - uint256(0), // value - refundAddress // refundAddress + uint256(100000), // gasLimit + uint256(0), // value + refundAddress // refundAddress ); // Set overrides on the plug srcPlug.setOverrides(overrides); - + bytes memory payload = abi.encode("test data"); uint256 msgValue = MIN_FEES + 0.001 ether; - + // Get counters before the call uint64 triggerCounterBefore = socket.triggerCounter(); uint40 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + // Calculate expected values bytes32 expectedTriggerId = calculateTriggerId(address(socket), triggerCounterBefore); - bytes32 expectedPayloadId = calculatePayloadId(expectedTriggerId, payloadCounterBefore, DST_CHAIN); - + bytes32 expectedPayloadId = calculatePayloadId( + expectedTriggerId, + payloadCounterBefore, + DST_CHAIN + ); + // Create digest params for the expected event DigestParams memory expectedDigestParams = _createDigestParams( expectedPayloadId, @@ -434,7 +473,7 @@ contract MessageSwitchboardTest is Test, Utils { payload ); bytes32 expectedDigest = calculateDigest(expectedDigestParams); - + // Expect the event with calculated values vm.expectEmit(true, true, false, false); emit MessageOutbound( @@ -447,28 +486,28 @@ contract MessageSwitchboardTest is Test, Utils { 0, address(0) ); - + vm.deal(address(srcPlug), 10 ether); bytes32 actualTriggerId = srcPlug.triggerSocket{value: msgValue}(payload); - + // Verify trigger ID matches assertEq(actualTriggerId, expectedTriggerId); - + // Verify payload counter increased assertEq(messageSwitchboard.payloadCounter(), payloadCounterBefore + 1); - + // Verify fees stored - (, address storedRefundAddr,,,) = messageSwitchboard.payloadFees(expectedPayloadId); + (, address storedRefundAddr, , , ) = messageSwitchboard.payloadFees(expectedPayloadId); assertEq(storedRefundAddr, refundAddress); } - + function test_processTrigger_Native_InsufficientValue_Reverts() public { // Setup sibling config _setupSiblingConfig(); - + // Set minimum fees _setupMinFees(); - + // Try with insufficient value bytes memory overrides = abi.encode( uint8(1), // version @@ -477,68 +516,72 @@ contract MessageSwitchboardTest is Test, Utils { 0, refundAddress ); - + // Set overrides on the plug srcPlug.setOverrides(overrides); - + vm.deal(address(srcPlug), 10 ether); vm.prank(address(srcPlug)); vm.expectRevert(MessageSwitchboard.InsufficientMsgValue.selector); srcPlug.triggerSocket{value: MIN_FEES - 1}(abi.encode("test")); } - + function test_processTrigger_Native_SiblingSocketNotFound_Reverts() public { bytes memory overrides = abi.encode(uint8(1), DST_CHAIN, 100000, 0, refundAddress); - + // Set overrides on the plug srcPlug.setOverrides(overrides); - + vm.prank(address(srcPlug)); vm.expectRevert(MessageSwitchboard.SiblingSocketNotFound.selector); srcPlug.triggerSocket(abi.encode("test")); } - + // ============================================ // CRITICAL TESTS - GROUP 3: processTrigger - Sponsored Flow // ============================================ - + function test_processTrigger_Sponsored_Success() public { // Setup sibling config _setupSiblingConfig(); - + // Sponsor approves plug _approvePlugForSponsor(); - + // Prepare overrides for version 2 (Sponsored) bytes memory overrides = abi.encode( - uint8(2), // version + uint8(2), // version DST_CHAIN, - uint256(100000), // gasLimit - uint256(0), // value + uint256(100000), // gasLimit + uint256(0), // value uint256(10 ether), // maxFees - sponsor // sponsor + sponsor // sponsor ); - + bytes memory payload = abi.encode("sponsored test"); - + // Get counters before the call uint64 triggerCounterBefore = socket.triggerCounter(); uint40 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + // Calculate expected values bytes32 expectedTriggerId = calculateTriggerId(address(socket), triggerCounterBefore); - bytes32 expectedPayloadId = calculatePayloadId(expectedTriggerId, payloadCounterBefore, DST_CHAIN); - + bytes32 expectedPayloadId = calculatePayloadId( + expectedTriggerId, + payloadCounterBefore, + DST_CHAIN + ); + // Set overrides on the plug srcPlug.setOverrides(overrides); - + // Only check indexed fields (payloadId, dstChainSlug, sponsor) - skip data fields for struct comparison vm.expectEmit(true, true, false, false); emit MessageOutbound( expectedPayloadId, DST_CHAIN, bytes32(0), // digest - not checked - DigestParams({ // Only structure matters, values not checked + DigestParams({ // Only structure matters, values not checked socket: bytes32(0), transmitter: bytes32(0), payloadId: bytes32(0), @@ -552,97 +595,92 @@ contract MessageSwitchboardTest is Test, Utils { prevBatchDigestHash: bytes32(0), extraData: "" }), - true, // isSponsored + true, // isSponsored 0, 10 ether, sponsor ); - + vm.prank(address(srcPlug)); bytes32 actualTriggerId = srcPlug.triggerSocket(payload); - + // Verify trigger ID matches assertEq(actualTriggerId, expectedTriggerId); - + // Verify sponsored fees were stored - (uint256 maxFees,) = messageSwitchboard.sponsoredPayloadFees(expectedPayloadId); + (uint256 maxFees, ) = messageSwitchboard.sponsoredPayloadFees(expectedPayloadId); assertEq(maxFees, 10 ether); } - + function test_processTrigger_Sponsored_NotApproved_Reverts() public { // Setup sibling config _setupSiblingConfig(); - + // Don't approve - try without approval - bytes memory overrides = abi.encode( - uint8(2), - DST_CHAIN, - 100000, - 0, - 10 ether, - sponsor - ); - + bytes memory overrides = abi.encode(uint8(2), DST_CHAIN, 100000, 0, 10 ether, sponsor); + // Set overrides on the plug srcPlug.setOverrides(overrides); - + vm.prank(address(srcPlug)); vm.expectRevert(MessageSwitchboard.PlugNotApprovedBySponsor.selector); srcPlug.triggerSocket(abi.encode("test")); } - + function test_processTrigger_UnsupportedVersion_Reverts() public { bytes memory overrides = abi.encode(uint8(99), DST_CHAIN, 100000, 0, refundAddress); - + // Set overrides on the plug srcPlug.setOverrides(overrides); - + vm.prank(address(srcPlug)); vm.expectRevert(MessageSwitchboard.UnsupportedOverrideVersion.selector); srcPlug.triggerSocket(abi.encode("test")); } - + // ============================================ // CRITICAL TESTS - GROUP 4: Enhanced Attest // ============================================ - + function test_attest_SuccessWithTargetVerification() public { // Setup sibling config _setupSiblingConfig(); - + // Create digest params (using any valid values since we're just testing attestation) bytes32 triggerId = bytes32(uint256(0x1234)); bytes memory payload = abi.encode("test"); bytes32 payloadId = bytes32(uint256(0x5678)); - + DigestParams memory digestParams = _createDigestParams(payloadId, triggerId, payload); - + // Calculate the actual digest from digestParams (as done in MessageSwitchboard._createDigest) bytes32 digest = calculateDigest(digestParams); - + // Create watcher signature - attest signs: keccak256(abi.encodePacked(switchboardAddress, chainSlug, digest)) - bytes32 signatureDigest = keccak256(abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, digest)); + bytes32 signatureDigest = keccak256( + abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, digest) + ); bytes memory signature = createSignature(signatureDigest, watcherPrivateKey); - + // Register this digest as attested (simulating the flow) vm.prank(getWatcherAddress()); vm.expectEmit(true, false, true, false); emit Attested(payloadId, digest, getWatcherAddress()); messageSwitchboard.attest(digestParams, signature); - + // Verify it's attested assertTrue(messageSwitchboard.isAttested(digest)); } - + function test_attest_InvalidTarget_Reverts() public { // Setup sibling config _setupSiblingConfig(); - + // Create digest with wrong target (address(0x9999) is not registered as a sibling plug) bytes32 triggerId = bytes32(uint256(0x1234)); bytes memory payload = abi.encode("test"); bytes32 payloadId = bytes32(uint256(0x5678)); - + // Create digest params with invalid target bytes32 siblingSocket = messageSwitchboard.siblingSockets(DST_CHAIN); DigestParams memory digestParams = DigestParams({ @@ -659,292 +697,309 @@ contract MessageSwitchboardTest is Test, Utils { prevBatchDigestHash: triggerId, extraData: abi.encode(SRC_CHAIN, toBytes32Format(address(srcPlug))) }); - + // Calculate the actual digest from digestParams (signature needs valid digest first) bytes32 digest = calculateDigest(digestParams); - + // Create watcher signature with correct digest (this will pass watcher check) - bytes32 signatureDigest = keccak256(abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, digest)); + bytes32 signatureDigest = keccak256( + abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, digest) + ); bytes memory signature = createSignature(signatureDigest, watcherPrivateKey); - + vm.prank(getWatcherAddress()); vm.expectRevert(MessageSwitchboard.InvalidTargetVerification.selector); messageSwitchboard.attest(digestParams, signature); } - + function test_attest_InvalidWatcher_Reverts() public { // Setup sibling config _setupSiblingConfig(); - + bytes32 payloadId = bytes32(uint256(0x5678)); bytes32 triggerId = bytes32(uint256(0x1234)); - DigestParams memory digestParams = _createDigestParams(payloadId, triggerId, abi.encode("test")); - + DigestParams memory digestParams = _createDigestParams( + payloadId, + triggerId, + abi.encode("test") + ); + // Calculate the actual digest from digestParams bytes32 digest = calculateDigest(digestParams); - + // Invalid signature from non-watcher (random private key) - bytes32 signatureDigest = keccak256(abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, digest)); - bytes memory signature = createSignature(signatureDigest, 0x2222222222222222222222222222222222222222222222222222222222222222); // Random key - + bytes32 signatureDigest = keccak256( + abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, digest) + ); + bytes memory signature = createSignature( + signatureDigest, + 0x2222222222222222222222222222222222222222222222222222222222222222 + ); // Random key + vm.prank(address(0x9999)); vm.expectRevert(MessageSwitchboard.WatcherNotFound.selector); messageSwitchboard.attest(digestParams, signature); } - + function test_attest_AlreadyAttested_Reverts() public { // Setup sibling config _setupSiblingConfig(); - + bytes32 payloadId = bytes32(uint256(0x5678)); bytes32 triggerId = bytes32(uint256(0x1234)); - DigestParams memory digestParams = _createDigestParams(payloadId, triggerId, abi.encode("test")); - + DigestParams memory digestParams = _createDigestParams( + payloadId, + triggerId, + abi.encode("test") + ); + // Calculate the actual digest from digestParams bytes32 digest = calculateDigest(digestParams); - + // Create watcher signature - bytes32 signatureDigest = keccak256(abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, digest)); + bytes32 signatureDigest = keccak256( + abi.encodePacked(toBytes32Format(address(messageSwitchboard)), SRC_CHAIN, digest) + ); bytes memory signature = createSignature(signatureDigest, watcherPrivateKey); - + // First attest - should succeed vm.prank(getWatcherAddress()); messageSwitchboard.attest(digestParams, signature); - + // Second attest - should revert vm.prank(getWatcherAddress()); vm.expectRevert(MessageSwitchboard.AlreadyAttested.selector); messageSwitchboard.attest(digestParams, signature); } - + // ============================================ // IMPORTANT TESTS - GROUP 5: Sponsor Approvals // ============================================ - + function test_approvePlug_Success() public { vm.expectEmit(true, true, false, false); emit PlugApproved(sponsor, address(srcPlug)); - + vm.prank(sponsor); messageSwitchboard.approvePlug(address(srcPlug)); - + assertTrue(messageSwitchboard.sponsorApprovals(sponsor, address(srcPlug))); } - + function test_approvePlugs_Batch_Success() public { address[] memory plugs = new address[](2); plugs[0] = address(srcPlug); plugs[1] = address(dstPlug); - + vm.startPrank(sponsor); vm.expectEmit(true, true, false, false); emit PlugApproved(sponsor, address(srcPlug)); - + vm.expectEmit(true, true, false, false); emit PlugApproved(sponsor, address(dstPlug)); - + messageSwitchboard.approvePlugs(plugs); - + assertTrue(messageSwitchboard.sponsorApprovals(sponsor, address(srcPlug))); assertTrue(messageSwitchboard.sponsorApprovals(sponsor, address(dstPlug))); - + vm.stopPrank(); } - + function test_revokePlug_Success() public { // First approve vm.prank(sponsor); messageSwitchboard.approvePlug(address(srcPlug)); assertTrue(messageSwitchboard.sponsorApprovals(sponsor, address(srcPlug))); - + // Now revoke vm.expectEmit(true, true, false, false); emit PlugRevoked(sponsor, address(srcPlug)); - + vm.prank(sponsor); messageSwitchboard.revokePlug(address(srcPlug)); - + assertFalse(messageSwitchboard.sponsorApprovals(sponsor, address(srcPlug))); } - + function test_revokePlugs_Batch_Success() public { address[] memory plugs = new address[](2); plugs[0] = address(srcPlug); plugs[1] = address(dstPlug); - + vm.startPrank(sponsor); messageSwitchboard.approvePlugs(plugs); vm.stopPrank(); - + // Now revoke batch vm.startPrank(sponsor); vm.expectEmit(true, true, false, false); emit PlugRevoked(sponsor, address(srcPlug)); - + vm.expectEmit(true, true, false, false); emit PlugRevoked(sponsor, address(dstPlug)); - + messageSwitchboard.revokePlugs(plugs); - + assertFalse(messageSwitchboard.sponsorApprovals(sponsor, address(srcPlug))); assertFalse(messageSwitchboard.sponsorApprovals(sponsor, address(dstPlug))); - + vm.stopPrank(); } - + // ============================================ // CRITICAL TESTS - GROUP 6: Refund Flow // ============================================ - + function test_markRefundEligible_Success() public { // Setup and create a payload _setupCompleteNative(); - + bytes32 payloadId = _createNativePayload("test", MIN_FEES); - + // Verify fees exist - (uint256 nativeFees,,,,) = messageSwitchboard.payloadFees(payloadId); + (uint256 nativeFees, , , , ) = messageSwitchboard.payloadFees(payloadId); assertEq(nativeFees, MIN_FEES); - + // Mark eligible bytes memory signature = _createWatcherSignature(payloadId); - + vm.expectEmit(true, true, false, false); emit RefundEligibilityMarked(payloadId, getWatcherAddress()); - + vm.prank(getWatcherAddress()); messageSwitchboard.markRefundEligible(payloadId, signature); - + // Verify marked eligible - (,, bool isEligible,,) = messageSwitchboard.payloadFees(payloadId); + (, , bool isEligible, , ) = messageSwitchboard.payloadFees(payloadId); assertTrue(isEligible); } - + function test_markRefundEligible_NoFeesToRefund_Reverts() public { // Create a non-existent payloadId (one that was never created) bytes32 payloadId = bytes32(uint256(0x9999)); - + // Create valid watcher signature (this will pass watcher check) bytes memory signature = _createWatcherSignature(payloadId); - + // Should revert with NoFeesToRefund because payload doesn't exist vm.prank(getWatcherAddress()); vm.expectRevert(MessageSwitchboard.NoFeesToRefund.selector); messageSwitchboard.markRefundEligible(payloadId, signature); } - + function test_refund_Success() public { // Setup and create payload _setupCompleteNative(); - + bytes32 payloadId = _createNativePayload("test", MIN_FEES); - + // Mark eligible bytes memory signature = _createWatcherSignature(payloadId); vm.prank(getWatcherAddress()); messageSwitchboard.markRefundEligible(payloadId, signature); - + // Refund uint256 balanceBefore = refundAddress.balance; vm.deal(address(messageSwitchboard), MIN_FEES); - + vm.expectEmit(true, true, false, false); emit Refunded(payloadId, refundAddress, MIN_FEES); - + vm.prank(refundAddress); messageSwitchboard.refund(payloadId); - + assertEq(refundAddress.balance, balanceBefore + MIN_FEES); - + // Verify marked as refunded - (,,, bool isRefunded,) = messageSwitchboard.payloadFees(payloadId); + (, , , bool isRefunded, ) = messageSwitchboard.payloadFees(payloadId); assertTrue(isRefunded); } - + function test_refund_NotEligible_Reverts() public { bytes32 payloadId = keccak256("test"); - + vm.prank(refundAddress); vm.expectRevert(MessageSwitchboard.RefundNotEligible.selector); messageSwitchboard.refund(payloadId); } - + function test_refund_UnauthorizedCaller_Reverts() public { _setupCompleteNative(); - + // Create a payload and get its ID bytes32 payloadId = _createNativePayload("test", MIN_FEES); - + // Mark eligible bytes memory signature = _createWatcherSignature(payloadId); vm.prank(getWatcherAddress()); messageSwitchboard.markRefundEligible(payloadId, signature); - + vm.deal(address(messageSwitchboard), MIN_FEES); - + // Try to refund from wrong address vm.prank(address(0x9999)); vm.expectRevert(MessageSwitchboard.UnauthorizedRefund.selector); messageSwitchboard.refund(payloadId); } - + // ============================================ // IMPORTANT TESTS - GROUP 7: Fee Updates // ============================================ - + function test_setMinMsgValueFeesOwner_Success() public { uint256 newFee = 0.002 ether; - + vm.expectEmit(true, true, true, false); emit MinMsgValueFeesSet(DST_CHAIN, newFee, owner); - + vm.prank(owner); messageSwitchboard.setMinMsgValueFeesOwner(DST_CHAIN, newFee); - + assertEq(messageSwitchboard.minMsgValueFees(DST_CHAIN), newFee); } - + function test_setMinMsgValueFeesBatchOwner_Success() public { uint32[] memory chainSlugs = new uint32[](2); chainSlugs[0] = DST_CHAIN; chainSlugs[1] = 3; - + uint256[] memory minFees = new uint256[](2); minFees[0] = 0.001 ether; minFees[1] = 0.002 ether; - + vm.prank(owner); messageSwitchboard.setMinMsgValueFeesBatchOwner(chainSlugs, minFees); - + assertEq(messageSwitchboard.minMsgValueFees(chainSlugs[0]), 0.001 ether); assertEq(messageSwitchboard.minMsgValueFees(chainSlugs[1]), 0.002 ether); } - + function test_setMinMsgValueFeesBatchOwner_ArrayLengthMismatch_Reverts() public { uint32[] memory chainSlugs = new uint32[](2); chainSlugs[0] = DST_CHAIN; chainSlugs[1] = 3; - + uint256[] memory minFees = new uint256[](1); // Length mismatch minFees[0] = 0.001 ether; - + vm.prank(owner); vm.expectRevert(MessageSwitchboard.ArrayLengthMismatch.selector); messageSwitchboard.setMinMsgValueFeesBatchOwner(chainSlugs, minFees); } - + // ============================================ // IMPORTANT TESTS - GROUP 8: increaseFeesForPayload // ============================================ - + function test_increaseFeesForPayload_Native_Success() public { // Setup sibling config and min fees _setupCompleteNative(); - + bytes memory feesData = abi.encode(uint8(1)); // Native fees type uint256 additionalFees = 0.01 ether; uint256 initialFees = MIN_FEES + 0.001 ether; - + // First create a payload via processTrigger bytes memory overrides = abi.encode( uint8(1), // version @@ -956,44 +1011,44 @@ contract MessageSwitchboardTest is Test, Utils { address(0), // sponsor false // isSponsored ); - + // Set overrides on the plug srcPlug.setOverrides(overrides); - + // Get counters before creating payload uint64 triggerCounterBefore = socket.triggerCounter(); uint40 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + vm.deal(address(srcPlug), 1 ether); vm.prank(address(srcPlug)); bytes32 actualTriggerId = srcPlug.triggerSocket{value: initialFees}(abi.encode("payload")); - + // Calculate the actual payloadId bytes32 payloadId = calculatePayloadId(actualTriggerId, payloadCounterBefore, DST_CHAIN); - + // Verify initial fees were stored - (uint256 nativeFeesBefore,,,,) = messageSwitchboard.payloadFees(payloadId); + (uint256 nativeFeesBefore, , , , ) = messageSwitchboard.payloadFees(payloadId); assertEq(nativeFeesBefore, initialFees); - + // Now test fee increase vm.expectEmit(true, true, false, false); emit FeesIncreased(payloadId, additionalFees, feesData); - + vm.prank(address(srcPlug)); srcPlug.increaseFeesForPayload{value: additionalFees}(payloadId, feesData); - + // Verify fees increased - (uint256 nativeFeesAfter,,,,) = messageSwitchboard.payloadFees(payloadId); + (uint256 nativeFeesAfter, , , , ) = messageSwitchboard.payloadFees(payloadId); assertEq(nativeFeesAfter, initialFees + additionalFees); } - + function test_increaseFeesForPayload_Sponsored_Success() public { // Setup sibling config and sponsor approval _setupCompleteSponsored(); - + uint256 newMaxFees = 0.05 ether; bytes memory feesData = abi.encode(uint8(2), newMaxFees); // Sponsored fees type + new maxFees - + // First create a sponsored payload via processTrigger bytes memory overrides = abi.encode( uint8(2), // version @@ -1003,44 +1058,44 @@ contract MessageSwitchboardTest is Test, Utils { uint256(0.02 ether), // maxFees sponsor // sponsor ); - + // Set overrides on the plug srcPlug.setOverrides(overrides); - + // Get counters before creating payload uint64 triggerCounterBefore = socket.triggerCounter(); uint40 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + vm.prank(address(srcPlug)); bytes32 actualTriggerId = srcPlug.triggerSocket(abi.encode("payload")); - + // Calculate the actual payloadId bytes32 payloadId = calculatePayloadId(actualTriggerId, payloadCounterBefore, DST_CHAIN); - + // Verify initial maxFees were stored - (uint256 maxFeesBefore,) = messageSwitchboard.sponsoredPayloadFees(payloadId); + (uint256 maxFeesBefore, ) = messageSwitchboard.sponsoredPayloadFees(payloadId); assertEq(maxFeesBefore, 0.02 ether); - + // Now test sponsored fee increase vm.expectEmit(true, true, false, false); emit SponsoredFeesIncreased(payloadId, newMaxFees, address(srcPlug)); - + vm.prank(address(srcPlug)); srcPlug.increaseFeesForPayload(payloadId, feesData); - + // Verify maxFees updated - (uint256 maxFeesAfter,) = messageSwitchboard.sponsoredPayloadFees(payloadId); + (uint256 maxFeesAfter, ) = messageSwitchboard.sponsoredPayloadFees(payloadId); assertEq(maxFeesAfter, newMaxFees); } - + function test_increaseFeesForPayload_UnauthorizedPlug_Reverts() public { // Setup sibling config and min fees _setupCompleteNative(); - + bytes memory feesData = abi.encode(uint8(1)); // Native fees type uint256 additionalFees = 0.01 ether; uint256 initialFees = MIN_FEES + 0.001 ether; - + // Create payload with srcPlug bytes memory overrides = abi.encode( uint8(1), // version @@ -1052,32 +1107,32 @@ contract MessageSwitchboardTest is Test, Utils { address(0), // sponsor false // isSponsored ); - + // Set overrides on the plug srcPlug.setOverrides(overrides); - + // Get counters before creating payload uint40 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + vm.deal(address(srcPlug), 1 ether); vm.prank(address(srcPlug)); bytes32 actualTriggerId = srcPlug.triggerSocket{value: initialFees}(abi.encode("payload")); - + // Calculate the actual payloadId bytes32 payloadId = calculatePayloadId(actualTriggerId, payloadCounterBefore, DST_CHAIN); - + // Try to increase fees with different plug - should revert because plug doesn't match vm.deal(address(dstPlug), 1 ether); vm.expectRevert(MessageSwitchboard.UnauthorizedFeeIncrease.selector); vm.prank(address(dstPlug)); // Different plug (not the one that created the payload) dstPlug.increaseFeesForPayload{value: additionalFees}(payloadId, feesData); } - + function test_increaseFeesForPayload_InvalidFeesType_Reverts() public { bytes memory feesData = abi.encode(uint8(3)); // Invalid fees type uint256 additionalFees = 0.01 ether; bytes32 payloadId = bytes32(uint256(0x9999)); // Non-existent payloadId - + // Socket's increaseFeesForPayload calls switchboard's increaseFeesForPayload with plug as msg.sender // Switchboard will decode feesType and revert with InvalidFeesType before checking authorization vm.deal(address(srcPlug), 1 ether); @@ -1085,16 +1140,16 @@ contract MessageSwitchboardTest is Test, Utils { vm.expectRevert(MessageSwitchboard.InvalidFeesType.selector); srcPlug.increaseFeesForPayload{value: additionalFees}(payloadId, feesData); } - + function test_increaseFeesForPayload_NotSocket_Reverts() public { bytes32 payloadId = keccak256("payload"); bytes memory feesData = abi.encode(uint8(1)); // Native fees type uint256 additionalFees = 0.01 ether; - + vm.expectRevert(SwitchboardBase.NotSocket.selector); messageSwitchboard.increaseFeesForPayload{value: additionalFees}( - payloadId, - address(srcPlug), + payloadId, + address(srcPlug), feesData ); } @@ -1103,7 +1158,7 @@ contract MessageSwitchboardTest is Test, Utils { /** * @title MessageSwitchboard Test Suite * @notice Comprehensive tests for MessageSwitchboard unique functionality - * + * * Test Coverage: * - Sibling management (setSiblingConfig, registerSibling) * - processTrigger Native flow (version 1) with fee handling @@ -1114,8 +1169,7 @@ contract MessageSwitchboardTest is Test, Utils { * - Refund flow (markRefundEligible + refund) * - Fee updates (owner + batch) * - increaseFeesForPayload - * + * * Total Tests: ~40 * Coverage: All critical and important MessageSwitchboard functionality */ - From e7970bf354afeb11451d357e0748faf872d72cc9 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 7 Nov 2025 12:53:01 +0530 Subject: [PATCH 06/10] fix: trigger --- contracts/evmx/watcher/Watcher.sol | 3 ++ contracts/protocol/Socket.sol | 4 +- .../protocol/interfaces/ISwitchboard.sol | 2 +- .../protocol/switchboard/FastSwitchboard.sol | 21 ++++++++- .../switchboard/MessageSwitchboard.sol | 43 +++++++++++++++---- .../protocol/switchboard/SwitchboardBase.sol | 3 ++ contracts/utils/common/Structs.sol | 1 + 7 files changed, 64 insertions(+), 13 deletions(-) diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index 6d61e704..4a4934fd 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -213,6 +213,9 @@ contract Watcher is Initializable, Configurations { if (!isValidPlug[appGateway][params_.chainSlug][params_.plug]) revert InvalidCallerTriggered(); + uint256 deadline = abi.decode(params_.overrides, (uint256)); + if (deadline < block.timestamp) revert DeadlinePassed(); + IERC20(address(feesManager__())).transferFrom(appGateway, address(this), triggerFees); triggerFromChainSlug = params_.chainSlug; triggerFromPlug = params_.plug; diff --git a/contracts/protocol/Socket.sol b/contracts/protocol/Socket.sol index a05486f5..3e0ec307 100644 --- a/contracts/protocol/Socket.sol +++ b/contracts/protocol/Socket.sol @@ -213,7 +213,7 @@ contract Socket is SocketUtils { triggerId = _encodeTriggerId(); // todo: need gas limit? - ISwitchboard(switchboardAddress).processTrigger{value: value_}( + bytes memory overridesData = ISwitchboard(switchboardAddress).processTrigger{value: value_}( plug_, triggerId, data_, @@ -225,7 +225,7 @@ contract Socket is SocketUtils { bytes32(0), // TODO: clean this up switchboardId, toBytes32Format(plug_), - plugOverrides, + overridesData, data_ ); } diff --git a/contracts/protocol/interfaces/ISwitchboard.sol b/contracts/protocol/interfaces/ISwitchboard.sol index 5ce1dcef..0fe18a17 100644 --- a/contracts/protocol/interfaces/ISwitchboard.sol +++ b/contracts/protocol/interfaces/ISwitchboard.sol @@ -36,7 +36,7 @@ interface ISwitchboard { bytes32 triggerId_, bytes calldata payload_, bytes calldata overrides_ - ) external payable; + ) external payable returns (bytes memory overridesData); /** * @notice Gets the transmitter for a given payload diff --git a/contracts/protocol/switchboard/FastSwitchboard.sol b/contracts/protocol/switchboard/FastSwitchboard.sol index 9a5ebca9..8dcd7154 100644 --- a/contracts/protocol/switchboard/FastSwitchboard.sol +++ b/contracts/protocol/switchboard/FastSwitchboard.sol @@ -11,6 +11,7 @@ import {toBytes32Format} from "../../utils/common/Converters.sol"; * that enables payload attestations from watchers */ contract FastSwitchboard is SwitchboardBase { + uint256 public defaultDeadline = 1 days; // used to track if watcher have attested a payload // payloadId => isAttested mapping(bytes32 => bool) public isAttested; @@ -25,6 +26,10 @@ contract FastSwitchboard is SwitchboardBase { error InvalidSource(); // Event emitted when watcher attests a payload event Attested(bytes32 digest, address watcher); + // Event emitted when reverting trigger is set + event RevertingTriggerSet(bytes32 triggerId, bool isReverting); + // Event emitted when default deadline is set + event DefaultDeadlineSet(uint256 defaultDeadline); /** * @notice Event emitted when plug configuration is updated */ @@ -83,7 +88,11 @@ contract FastSwitchboard is SwitchboardBase { bytes32 triggerId_, bytes calldata payload_, bytes calldata overrides_ - ) external payable virtual {} + ) external payable virtual returns (bytes memory overridesData) { + uint256 deadline = abi.decode(overrides_, (uint256)); + if (deadline == 0) return abi.encode(block.timestamp + defaultDeadline); + return overrides_; + } /** * @inheritdoc ISwitchboard @@ -103,6 +112,16 @@ contract FastSwitchboard is SwitchboardBase { emit PlugConfigUpdated(plug_, appGatewayId_); } + function setRevertingTrigger(bytes32 triggerId_, bool isReverting_) external onlyOwner { + revertingTriggers[triggerId_] = isReverting_; + emit RevertingTriggerSet(triggerId_, isReverting_); + } + + function setDefaultDeadline(uint256 defaultDeadline_) external onlyOwner { + defaultDeadline = defaultDeadline_; + emit DefaultDeadlineSet(defaultDeadline_); + } + /** * @inheritdoc ISwitchboard */ diff --git a/contracts/protocol/switchboard/MessageSwitchboard.sol b/contracts/protocol/switchboard/MessageSwitchboard.sol index d9746527..cf6983a1 100644 --- a/contracts/protocol/switchboard/MessageSwitchboard.sol +++ b/contracts/protocol/switchboard/MessageSwitchboard.sol @@ -43,6 +43,8 @@ contract MessageSwitchboard is SwitchboardBase { // nonce tracking for fee updates: updater => nonce => used mapping(address => mapping(uint256 => bool)) public usedNonces; + uint256 public defaultDeadline = 1 days; + // Error emitted when a payload is already attested by watcher. error AlreadyAttested(); // Error emitted when watcher is not valid @@ -117,6 +119,9 @@ contract MessageSwitchboard is SwitchboardBase { address indexed plug ); + // Event emitted when reverting trigger is set + event RevertingTriggerSet(bytes32 triggerId, bool isReverting); + /** * @dev Constructor function for the MessageSwitchboard contract * @param chainSlug_ Chain slug of the chain where the contract is deployed @@ -146,6 +151,11 @@ contract MessageSwitchboard is SwitchboardBase { emit SiblingConfigSet(chainSlug_, socket_, switchboard_); } + function setRevertingTrigger(bytes32 triggerId_, bool isReverting_) external onlyOwner { + revertingTriggers[triggerId_] = isReverting_; + emit RevertingTriggerSet(triggerId_, isReverting_); + } + /** * @dev Function to process trigger and create payload * @param plug_ Source plug address @@ -158,9 +168,10 @@ contract MessageSwitchboard is SwitchboardBase { bytes32 triggerId_, bytes calldata payload_, bytes calldata overrides_ - ) external payable override onlySocket { + ) external payable override onlySocket returns (bytes memory overridesData) { MessageOverrides memory overrides = _decodeOverrides(overrides_); _validateSibling(overrides.dstChainSlug, plug_); + overridesData = abi.encode(overrides); // Create digest and payload ID (common for both flows) ( @@ -228,13 +239,20 @@ contract MessageSwitchboard is SwitchboardBase { */ function _decodeOverrides( bytes calldata overrides_ - ) internal pure returns (MessageOverrides memory) { + ) internal returns (MessageOverrides memory) { uint8 version = abi.decode(overrides_, (uint8)); if (version == 1) { - // Version 1: Native flow - (, uint32 dstChainSlug, uint256 gasLimit, uint256 value, address refundAddress) = abi - .decode(overrides_, (uint8, uint32, uint256, uint256, address)); + // Version 1: Native flow + ( + , + uint32 dstChainSlug, + uint256 gasLimit, + uint256 value, + address refundAddress, + uint256 deadline + ) = abi.decode(overrides_, (uint8, uint32, uint256, uint256, address, uint256)); + if(deadline == 0) deadline = block.timestamp + defaultDeadline; return MessageOverrides({ @@ -244,7 +262,8 @@ contract MessageSwitchboard is SwitchboardBase { refundAddress: refundAddress, maxFees: 0, sponsor: address(0), - isSponsored: false + isSponsored: false, + deadline: deadline }); } else if (version == 2) { // Version 2: Sponsored flow @@ -254,8 +273,13 @@ contract MessageSwitchboard is SwitchboardBase { uint256 gasLimit, uint256 value, uint256 maxFees, - address sponsor - ) = abi.decode(overrides_, (uint8, uint32, uint256, uint256, uint256, address)); + address sponsor, + uint256 deadline + ) = abi.decode( + overrides_, + (uint8, uint32, uint256, uint256, uint256, address, uint256) + ); + if(deadline == 0) deadline = block.timestamp + defaultDeadline; return MessageOverrides({ @@ -265,7 +289,8 @@ contract MessageSwitchboard is SwitchboardBase { refundAddress: address(0), maxFees: maxFees, sponsor: sponsor, - isSponsored: true + isSponsored: true, + deadline: deadline }); } else { revert UnsupportedOverrideVersion(); diff --git a/contracts/protocol/switchboard/SwitchboardBase.sol b/contracts/protocol/switchboard/SwitchboardBase.sol index 1f4e3a19..3c12cebc 100644 --- a/contracts/protocol/switchboard/SwitchboardBase.sol +++ b/contracts/protocol/switchboard/SwitchboardBase.sol @@ -20,6 +20,9 @@ abstract contract SwitchboardBase is ISwitchboard, AccessControl { // switchboard id uint64 public switchboardId; + // mapping of trigger id to isReverting + mapping(bytes32 => bool) public revertingTriggers; + error NotSocket(); /** * @dev Constructor of SwitchboardBase diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index b65d7775..24b214cc 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -245,4 +245,5 @@ struct MessageOverrides { uint256 maxFees; address sponsor; bool isSponsored; + uint256 deadline; } From 0ccbc5197842d67edb27417e8c4d5d09760689c0 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 7 Nov 2025 13:56:05 +0530 Subject: [PATCH 07/10] fix: watcher and transmitter fees --- contracts/evmx/fees/FeesManager.sol | 16 ++++++++-------- contracts/evmx/interfaces/IFeesManager.sol | 2 +- contracts/evmx/watcher/Watcher.sol | 21 +++++++++++++-------- contracts/utils/common/Structs.sol | 1 + 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/contracts/evmx/fees/FeesManager.sol b/contracts/evmx/fees/FeesManager.sol index a3476d53..a14156ac 100644 --- a/contracts/evmx/fees/FeesManager.sol +++ b/contracts/evmx/fees/FeesManager.sol @@ -122,24 +122,24 @@ contract FeesManager is Credit { /// @param assignTo_ The address of the transmitter function unblockAndAssignCredits( bytes32 payloadId_, - address assignTo_ + address assignTo_, + uint256 amount_ ) external override onlyWatcher { uint256 blockedCredits_ = blockedCredits[payloadId_]; if (blockedCredits_ == 0) return; - address consumeFrom = watcher__().getPayload(payloadId_).consumeFrom; + Payload memory payload = watcher__().getPayload(payloadId_); + address consumeFrom = payload.consumeFrom; // Unblock credits from the original user - userBlockedCredits[consumeFrom] -= blockedCredits_; + userBlockedCredits[consumeFrom] -= amount_; + blockedCredits[payloadId_] -= amount_; // Burn tokens from the original user - _burn(consumeFrom, blockedCredits_); - + _burn(consumeFrom, amount_); // Mint tokens to the transmitter - _mint(assignTo_, blockedCredits_); + _mint(assignTo_, amount_); - // Clean up storage - delete blockedCredits[payloadId_]; emit CreditsUnblockedAndAssigned(payloadId_, consumeFrom, assignTo_, blockedCredits_); } diff --git a/contracts/evmx/interfaces/IFeesManager.sol b/contracts/evmx/interfaces/IFeesManager.sol index fd587cb5..a60b1ece 100644 --- a/contracts/evmx/interfaces/IFeesManager.sol +++ b/contracts/evmx/interfaces/IFeesManager.sol @@ -31,7 +31,7 @@ interface IFeesManager { function blockCredits(bytes32 payloadId_, address consumeFrom_, uint256 credits_) external; - function unblockAndAssignCredits(bytes32 payloadId_, address assignTo_) external; + function unblockAndAssignCredits(bytes32 payloadId_, address assignTo_, uint256 amount_) external; function unblockCredits(bytes32 payloadId_) external; diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index 4a4934fd..49364e2e 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -101,6 +101,7 @@ contract Watcher is Initializable, Configurations { callType: payloadData.overrideParams.callType, isPayloadCancelled: false, isPayloadExecuted: false, + isTransmitterFeesSettled: false, payloadPointer: nextPayloadCount++, asyncPromise: asyncPromise, appGateway: latestAppGateway, @@ -122,7 +123,6 @@ contract Watcher is Initializable, Configurations { params_.data, (PromiseReturnData, uint256) ); - _resolvePayload(resolvedPromise, feesUsed); } @@ -135,6 +135,11 @@ contract Watcher is Initializable, Configurations { if (p.isPayloadExecuted) return; if (p.isPayloadCancelled) return; + if (!p.isTransmitterFeesSettled) { + p.isTransmitterFeesSettled = true; + feesManager__().unblockAndAssignCredits(p.payloadId, transmitter, feesUsed_); + } + p.isPayloadExecuted = true; p.resolvedAt = block.timestamp; @@ -142,7 +147,9 @@ contract Watcher is Initializable, Configurations { bool success = _markResolved(resolvedPromise_); if (!success) return; - _settlePayload(resolvedPromise_.payloadId, feesUsed_); + feesManager__().unblockAndAssignCredits(p.payloadId, address(this), p.watcherFees); + feesManager__().unblockCredits(p.payloadId); + emit PayloadSettled(p.payloadId); emit PayloadResolved(resolvedPromise_.payloadId); } @@ -265,13 +272,11 @@ contract Watcher is Initializable, Configurations { if (r.isPayloadCancelled) revert PayloadAlreadyCancelled(); r.isPayloadCancelled = true; - _settlePayload(payloadId_, r.maxFees); - emit PayloadCancelled(payloadId_); - } + r.isTransmitterFeesSettled = true; - function _settlePayload(bytes32 payloadId_, uint256 feesUsed_) internal { - feesManager__().unblockAndAssignCredits(payloadId_, transmitter); - emit PayloadSettled(payloadId_); + feesManager__().unblockAndAssignCredits(payloadId_, transmitter, r.maxFees - r.watcherFees); + feesManager__().unblockAndAssignCredits(payloadId_, address(this), r.watcherFees); + emit PayloadCancelled(payloadId_); } function watcherMultiCall(WatcherMultiCallParams[] memory params_) external payable { diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index 24b214cc..f043e87f 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -151,6 +151,7 @@ struct Payload { bytes4 callType; bool isPayloadCancelled; bool isPayloadExecuted; + bool isTransmitterFeesSettled; uint256 payloadPointer; address asyncPromise; address appGateway; From 8cbd5b7202267a384dcc5008504d359013f6987d Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 7 Nov 2025 14:04:49 +0530 Subject: [PATCH 08/10] fix: promise retryable with deadline --- contracts/evmx/helpers/AsyncDeployer.sol | 9 ++++++-- contracts/evmx/helpers/AsyncPromise.sol | 27 +++++++++++++----------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/contracts/evmx/helpers/AsyncDeployer.sol b/contracts/evmx/helpers/AsyncDeployer.sol index 3c32c16b..e5bf7cb6 100644 --- a/contracts/evmx/helpers/AsyncDeployer.sol +++ b/contracts/evmx/helpers/AsyncDeployer.sol @@ -31,6 +31,9 @@ abstract contract AsyncDeployerStorage is IAsyncDeployer { // slot 54 uint256 public asyncPromiseCounter; + // slot 55 + uint256 public defaultDeadline; + // slots [55-104] reserved for gap uint256[50] _gap_after; @@ -50,9 +53,10 @@ contract AsyncDeployer is AsyncDeployerStorage, Initializable, AddressResolverUt /// @dev it deploys the forwarder and async promise implementations and beacons for them /// @dev this contract is owner of the beacons for upgrading later /// @param owner_ The address of the contract owner - function initialize(address owner_, address addressResolver_) public reinitializer(1) { + function initialize(address owner_, address addressResolver_, uint256 defaultDeadline_) public reinitializer(1) { _initializeOwner(owner_); _setAddressResolver(addressResolver_); + defaultDeadline = defaultDeadline_; forwarderImplementation = address(new Forwarder()); asyncPromiseImplementation = address(new AsyncPromise()); @@ -140,7 +144,8 @@ contract AsyncDeployer is AsyncDeployerStorage, Initializable, AddressResolverUt AsyncPromise.initialize.selector, payloadId_, invoker_, - address(addressResolver__) + address(addressResolver__), + defaultDeadline ); // creates salt with a counter diff --git a/contracts/evmx/helpers/AsyncPromise.sol b/contracts/evmx/helpers/AsyncPromise.sol index cc396d30..64f6a440 100644 --- a/contracts/evmx/helpers/AsyncPromise.sol +++ b/contracts/evmx/helpers/AsyncPromise.sol @@ -7,7 +7,7 @@ import {AddressResolverUtil} from "./AddressResolverUtil.sol"; import {IAppGateway} from "../interfaces/IAppGateway.sol"; import "../interfaces/IPromise.sol"; import "../../utils/RescueFundsLib.sol"; -import {NotInvoker, PayloadCountMismatch} from "../../utils/common/Errors.sol"; +import {NotInvoker, PayloadCountMismatch, DeadlinePassed} from "../../utils/common/Errors.sol"; abstract contract AsyncPromiseStorage is IPromise { // slots [0-49] reserved for gap @@ -30,6 +30,9 @@ abstract contract AsyncPromiseStorage is IPromise { /// @dev The callback will be executed on this address address public override localInvoker; + /// @notice The flag to check if the transmitter fees are settled + uint256 public promiseDeadline; + // slot 51 /// @notice The return data of the promise bytes public override returnData; @@ -60,6 +63,10 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil /// @notice Error thrown when attempting to resolve an already resolved promise. error PromiseAlreadyResolved(); + + /// @notice Error thrown when attempting to resolve an already onchain reverted promise. + error PromiseAlreadyOnchainReverted(); + /// @notice Only the local invoker can set then's promise callback error OnlyInvoker(); /// @notice Error thrown when attempting to set an already existing promise @@ -80,11 +87,13 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil function initialize( bytes32 payloadId_, address invoker_, - address addressResolver_ + address addressResolver_, + uint256 deadline_ ) public reinitializer(1) { localInvoker = invoker_; payloadId = payloadId_; _setAddressResolver(addressResolver_); + promiseDeadline = deadline_ + block.timestamp; } /// @notice Marks the promise as resolved and executes the callback if set. @@ -93,11 +102,8 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil function markResolved( PromiseReturnData memory resolvedPromise_ ) external override onlyWatcher returns (bool success) { - if ( - state == AsyncPromiseState.CALLBACK_REVERTING || - state == AsyncPromiseState.ONCHAIN_REVERTING || - state == AsyncPromiseState.RESOLVED - ) revert PromiseAlreadyResolved(); + if (block.timestamp > promiseDeadline) revert DeadlinePassed(); + if (state == AsyncPromiseState.RESOLVED || state == AsyncPromiseState.ONCHAIN_REVERTING) revert PromiseAlreadyResolvedOrOnchainReverted(); state = AsyncPromiseState.RESOLVED; // Call callback to app gateway @@ -125,11 +131,8 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil function markOnchainRevert( PromiseReturnData memory resolvedPromise_ ) external override onlyWatcher { - if ( - state == AsyncPromiseState.CALLBACK_REVERTING || - state == AsyncPromiseState.ONCHAIN_REVERTING || - state == AsyncPromiseState.RESOLVED - ) revert PromiseAlreadyResolved(); + if (block.timestamp > promiseDeadline) revert DeadlinePassed(); + if (state == AsyncPromiseState.RESOLVED || state == AsyncPromiseState.ONCHAIN_REVERTING) revert PromiseAlreadyResolved(); // to update the state in case selector is bytes(0) but reverting onchain state = AsyncPromiseState.ONCHAIN_REVERTING; From 5b62e53247b923eb6c6020315580402a5e6ee329 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Sat, 8 Nov 2025 00:52:13 +0530 Subject: [PATCH 09/10] fix: tests --- contracts/evmx/helpers/AsyncPromise.sol | 5 +--- test/SetupTest.t.sol | 4 ++- test/switchboard/MessageSwitchboard.t.sol | 30 ++++++++++++++--------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/contracts/evmx/helpers/AsyncPromise.sol b/contracts/evmx/helpers/AsyncPromise.sol index 64f6a440..5932aeea 100644 --- a/contracts/evmx/helpers/AsyncPromise.sol +++ b/contracts/evmx/helpers/AsyncPromise.sol @@ -64,9 +64,6 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil /// @notice Error thrown when attempting to resolve an already resolved promise. error PromiseAlreadyResolved(); - /// @notice Error thrown when attempting to resolve an already onchain reverted promise. - error PromiseAlreadyOnchainReverted(); - /// @notice Only the local invoker can set then's promise callback error OnlyInvoker(); /// @notice Error thrown when attempting to set an already existing promise @@ -103,7 +100,7 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil PromiseReturnData memory resolvedPromise_ ) external override onlyWatcher returns (bool success) { if (block.timestamp > promiseDeadline) revert DeadlinePassed(); - if (state == AsyncPromiseState.RESOLVED || state == AsyncPromiseState.ONCHAIN_REVERTING) revert PromiseAlreadyResolvedOrOnchainReverted(); + if (state == AsyncPromiseState.RESOLVED || state == AsyncPromiseState.ONCHAIN_REVERTING) revert PromiseAlreadyResolved(); state = AsyncPromiseState.RESOLVED; // Call callback to app gateway diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 18c3ce13..9d936600 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -50,6 +50,7 @@ contract SetupStore is Test, Utils { uint256 expiryTime = 86400; uint256 bidTimeout = 86400; + uint256 defaultDeadline = 86400; uint256 maxReAuctionCount = 10; uint256 auctionEndDelaySeconds = 0; uint256 maxScheduleDelayInSeconds = 86500; @@ -287,7 +288,8 @@ contract DeploySetup is SetupStore { abi.encodeWithSelector( AsyncDeployer.initialize.selector, watcherEOA, - address(addressResolver) + address(addressResolver), + defaultDeadline ) ); asyncDeployer = AsyncDeployer(asyncDeployerProxy); diff --git a/test/switchboard/MessageSwitchboard.t.sol b/test/switchboard/MessageSwitchboard.t.sol index 5dfb2065..781cfd59 100644 --- a/test/switchboard/MessageSwitchboard.t.sol +++ b/test/switchboard/MessageSwitchboard.t.sol @@ -219,7 +219,8 @@ contract MessageSwitchboardTest is Test, Utils { DST_CHAIN, uint256(100000), // gasLimit uint256(0), // value - refundAddress // refundAddress + refundAddress, // refundAddress + 86400 // deadline ); // Set overrides on the plug @@ -254,7 +255,8 @@ contract MessageSwitchboardTest is Test, Utils { uint256(100000), // gasLimit uint256(0), // value maxFees, // maxFees - sponsor // sponsor + sponsor, // sponsor + 86400 // deadline ); // Set overrides on the plug @@ -446,7 +448,8 @@ contract MessageSwitchboardTest is Test, Utils { DST_CHAIN, uint256(100000), // gasLimit uint256(0), // value - refundAddress // refundAddress + refundAddress, // refundAddress + 86400 // deadline ); // Set overrides on the plug srcPlug.setOverrides(overrides); @@ -514,7 +517,8 @@ contract MessageSwitchboardTest is Test, Utils { DST_CHAIN, 100000, 0, - refundAddress + refundAddress, + 86400 // deadline ); // Set overrides on the plug @@ -527,7 +531,7 @@ contract MessageSwitchboardTest is Test, Utils { } function test_processTrigger_Native_SiblingSocketNotFound_Reverts() public { - bytes memory overrides = abi.encode(uint8(1), DST_CHAIN, 100000, 0, refundAddress); + bytes memory overrides = abi.encode(uint8(1), DST_CHAIN, 100000, 0, refundAddress, 86400); // Set overrides on the plug srcPlug.setOverrides(overrides); @@ -555,7 +559,8 @@ contract MessageSwitchboardTest is Test, Utils { uint256(100000), // gasLimit uint256(0), // value uint256(10 ether), // maxFees - sponsor // sponsor + sponsor, // sponsor + 86400 // deadline ); bytes memory payload = abi.encode("sponsored test"); @@ -617,7 +622,7 @@ contract MessageSwitchboardTest is Test, Utils { _setupSiblingConfig(); // Don't approve - try without approval - bytes memory overrides = abi.encode(uint8(2), DST_CHAIN, 100000, 0, 10 ether, sponsor); + bytes memory overrides = abi.encode(uint8(2), DST_CHAIN, 100000, 0, 10 ether, sponsor, 86400); // Set overrides on the plug srcPlug.setOverrides(overrides); @@ -628,7 +633,7 @@ contract MessageSwitchboardTest is Test, Utils { } function test_processTrigger_UnsupportedVersion_Reverts() public { - bytes memory overrides = abi.encode(uint8(99), DST_CHAIN, 100000, 0, refundAddress); + bytes memory overrides = abi.encode(uint8(99), DST_CHAIN, 100000, 0, refundAddress, 86400); // Set overrides on the plug srcPlug.setOverrides(overrides); @@ -1009,7 +1014,8 @@ contract MessageSwitchboardTest is Test, Utils { refundAddress, // refundAddress uint256(0), // maxFees address(0), // sponsor - false // isSponsored + false, // isSponsored + 86400 // deadline ); // Set overrides on the plug @@ -1056,7 +1062,8 @@ contract MessageSwitchboardTest is Test, Utils { uint256(100000), // gasLimit uint256(0), // value uint256(0.02 ether), // maxFees - sponsor // sponsor + sponsor, // sponsor + 86400 // deadline ); // Set overrides on the plug @@ -1105,7 +1112,8 @@ contract MessageSwitchboardTest is Test, Utils { refundAddress, // refundAddress uint256(0), // maxFees address(0), // sponsor - false // isSponsored + false, // isSponsored + 86400 // deadline ); // Set overrides on the plug From 73e6c782c69b9011756b460ddbf4b51dd37974d7 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Wed, 12 Nov 2025 15:38:04 +0530 Subject: [PATCH 10/10] fix: bugs --- contracts/evmx/fees/FeesManager.sol | 2 +- contracts/evmx/helpers/AsyncPromise.sol | 2 +- hardhat-scripts/constants/constants.ts | 2 ++ hardhat-scripts/deploy/1.deploy.ts | 3 ++- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/contracts/evmx/fees/FeesManager.sol b/contracts/evmx/fees/FeesManager.sol index a14156ac..69ce7ee4 100644 --- a/contracts/evmx/fees/FeesManager.sol +++ b/contracts/evmx/fees/FeesManager.sol @@ -140,7 +140,7 @@ contract FeesManager is Credit { // Mint tokens to the transmitter _mint(assignTo_, amount_); - emit CreditsUnblockedAndAssigned(payloadId_, consumeFrom, assignTo_, blockedCredits_); + emit CreditsUnblockedAndAssigned(payloadId_, consumeFrom, assignTo_, amount_); } function unblockCredits(bytes32 payloadId_) external override onlyWatcher { diff --git a/contracts/evmx/helpers/AsyncPromise.sol b/contracts/evmx/helpers/AsyncPromise.sol index 5932aeea..96766440 100644 --- a/contracts/evmx/helpers/AsyncPromise.sol +++ b/contracts/evmx/helpers/AsyncPromise.sol @@ -30,7 +30,7 @@ abstract contract AsyncPromiseStorage is IPromise { /// @dev The callback will be executed on this address address public override localInvoker; - /// @notice The flag to check if the transmitter fees are settled + /// @notice The deadline timestamp (seconds since epoch) for promise resolution uint256 public promiseDeadline; // slot 51 diff --git a/hardhat-scripts/constants/constants.ts b/hardhat-scripts/constants/constants.ts index da2495ec..e04f3bc6 100644 --- a/hardhat-scripts/constants/constants.ts +++ b/hardhat-scripts/constants/constants.ts @@ -13,3 +13,5 @@ export const BYTES32_ZERO = ethers.constants.HashZero; export const MSG_SB_FEES = "100000000"; export const FEE_MANAGER_WRITE_MAX_FEES = ethers.utils.parseEther("10"); + +export const DEFAULT_DEADLINE = 86400; \ No newline at end of file diff --git a/hardhat-scripts/deploy/1.deploy.ts b/hardhat-scripts/deploy/1.deploy.ts index 3eb2c712..35e6792a 100644 --- a/hardhat-scripts/deploy/1.deploy.ts +++ b/hardhat-scripts/deploy/1.deploy.ts @@ -26,6 +26,7 @@ import { getFeePool, IMPLEMENTATION_SLOT, FEE_MANAGER_WRITE_MAX_FEES, + DEFAULT_DEADLINE, } from "../constants"; import { DeployParams, @@ -156,7 +157,7 @@ const deployEVMxContracts = async () => { deployUtils = await deployContractWithProxy( Contracts.AsyncDeployer, `contracts/evmx/helpers/AsyncDeployer.sol`, - [EVMxOwner, addressResolver.address], + [EVMxOwner, addressResolver.address, DEFAULT_DEADLINE], proxyFactory, deployUtils );