From cfc1708e67b3800246f9234bce28761afead1732 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Thu, 19 Jun 2025 17:33:39 +0200 Subject: [PATCH 01/18] WIP --- contracts/evmx/helpers/ForwarderSolana.sol | 171 +++++++++++++++ contracts/utils/common/Constants.sol | 3 + contracts/utils/common/Structs.sol | 22 ++ .../DeployEVMSolanaApps.s.sol | 70 ++++++ .../EvmSolanaOnchainCalls.s.sol | 199 ++++++++++++++++++ .../super-token/EvmSolanaAppGateway.sol | 165 +++++++++++++++ 6 files changed, 630 insertions(+) create mode 100644 contracts/evmx/helpers/ForwarderSolana.sol create mode 100644 script/super-token-solana/DeployEVMSolanaApps.s.sol create mode 100644 script/super-token-solana/EvmSolanaOnchainCalls.s.sol create mode 100644 test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol diff --git a/contracts/evmx/helpers/ForwarderSolana.sol b/contracts/evmx/helpers/ForwarderSolana.sol new file mode 100644 index 00000000..cb35813b --- /dev/null +++ b/contracts/evmx/helpers/ForwarderSolana.sol @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +// import "./interfaces/IAddressResolver.sol"; +// import "./interfaces/IMiddleware.sol"; +// import "./interfaces/IAppGateway.sol"; +// import "./interfaces/IPromise.sol"; +// import "./interfaces/IForwarder.sol"; +// import {AddressResolverUtil} from "./AddressResolverUtil.sol"; +// import {AsyncModifierNotUsed, NoAsyncPromiseFound, PromiseCallerMismatch, RequestCountMismatch, DeliveryHelperNotSet} from "../utils/common/Errors.sol"; +// import "solady/utils/Initializable.sol"; +// import {SolanaInstruction} from "../utils/common/Structs.sol"; +// import {CHAIN_SLUG_SOLANA_MAINNET, CHAIN_SLUG_SOLANA_DEVNET} from "../utils/common/Constants.sol"; + +import "solady/utils/Initializable.sol"; +import "./AddressResolverUtil.sol"; +import "../interfaces/IAddressResolver.sol"; +import "../interfaces/IAppGateway.sol"; +import "../interfaces/IForwarder.sol"; +import {QueueParams, 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"; + + +/// @title Forwarder Storage +/// @notice Storage contract for the Forwarder contract that contains the state variables +abstract contract ForwarderStorage is IForwarder { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + // slot 50 + /// @notice chain slug on which the contract is deployed + uint32 public chainSlug; + /// @notice Solana on-chain address associated with this forwarder + bytes32 public onChainAddress; + + // slot 51 + /// @notice caches the latest async promise address for the last call + address public latestAsyncPromise; + + // slot 52 + /// @notice the address of the contract that called the latest async promise + address public latestPromiseCaller; + /// @notice the request count of the latest async promise + uint40 public latestRequestCount; + + // slots [53-102] reserved for gap + // TODO:remove-after-review: - 12 for using bytes32 onChainAddress - is there any mainnet deployment which storage could be affected? + uint256[50] _gap_after; + + // slots 103-154 (51) reserved for addr resolver util +} + +/// @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(); + + 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_, + 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 Stores the callback address and data to be executed once the promise is resolved. + /// @dev This function should not be called before the fallback function. + /// @dev It resets the latest async promise address + /// @param selector_ The function selector for callback + /// @param data_ The data to be passed to callback + /// @return promise_ The address of the new promise + // TODO:GW: uncommenting this is making the deployment fail silently + // function then(bytes4 selector_, bytes memory data_) external returns (address promise_) { + // if (latestAsyncPromise == address(0)) revert NoAsyncPromiseFound(); + // if (latestPromiseCaller != msg.sender) revert PromiseCallerMismatch(); + // if (latestRequestCount != watcherPrecompile__().nextRequestCount()) + // revert RequestCountMismatch(); + + // address latestAsyncPromise_ = latestAsyncPromise; + // latestAsyncPromise = address(0); + + // promise_ = IPromise(latestAsyncPromise_).then(selector_, data_); + // } + + /// @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 Fallback function to process the contract calls to onChainAddress + /// @dev It queues the calls in the middleware and deploys the promise contract + function callSolana(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external { + if (address(deliveryHelper__()) == address(0)) { + revert DeliveryHelperNotSet(); + } + + // validates if the async modifier is set + bool isAsyncModifierSet = IAppGateway(msg.sender).isAsyncModifierSet(); + if (!isAsyncModifierSet) revert AsyncModifierNotUsed(); + + // Deploy a new async promise contract. + latestAsyncPromise = addressResolver__.deployAsyncPromiseContract(msg.sender); + + // set the latest promise caller and request count for validating if the future .then call is valid + latestPromiseCaller = msg.sender; + latestRequestCount = watcherPrecompile__().nextRequestCount(); + + // fetch the override params from app gateway + ( + Read isReadCall, + Parallel isParallelCall, + WriteFinality writeFinality, + uint256 readAt, + uint256 gasLimit, + uint256 value, + bytes32 sbType + ) = IAppGateway(msg.sender).getOverrideParams(); + + // TODO:GW: after POC make it work like below + // get the switchboard address from the watcher precompile config + // address switchboard = watcherPrecompileConfig().switchboards(chainSlug, sbType); + + bytes memory solanaPayload = abi.encode(solanaInstruction); + + // Queue the call in the middleware. + deliveryHelper__().queue( + QueuePayloadParams({ + chainSlug: chainSlug, + callType: isReadCall == Read.ON ? CallType.READ : CallType.WRITE, + isParallel: isParallelCall, + isPlug: IsPlug.NO, + writeFinality: writeFinality, + asyncPromise: latestAsyncPromise, + switchboard: switchboardSolana, + target: onChainAddress, + appGateway: msg.sender, + gasLimit: gasLimit, + value: value, + readAt: readAt, + payload: solanaPayload, + initCallData: bytes("") + }) + ); + } +} diff --git a/contracts/utils/common/Constants.sol b/contracts/utils/common/Constants.sol index 2afa6479..eecc138a 100644 --- a/contracts/utils/common/Constants.sol +++ b/contracts/utils/common/Constants.sol @@ -16,3 +16,6 @@ bytes32 constant FAST = keccak256("FAST"); uint256 constant PAYLOAD_SIZE_LIMIT = 24_500; uint16 constant MAX_COPY_BYTES = 2048; // 2KB + +uint32 constant CHAIN_SLUG_SOLANA_MAINNET = 10000001; +uint32 constant CHAIN_SLUG_SOLANA_DEVNET = 10000002; diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index 244221a3..bbfb46e7 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -209,3 +209,25 @@ struct RequestParams { uint256 writeCount; bytes onCompleteData; } + +struct SolanaInstruction { + SolanaInstructionData data; + SolanaInstructionDataDescription description; +} + +struct SolanaInstructionData { + bytes32 programId; + bytes32[] accounts; + bytes8 instructionDiscriminator; + // TODO:GW: in one of functionArguments is an array it might need a special handling and encoding + // for now we assume the all functionArguments are simple types (uint256, address, bool, etc.) not complex types (struct, array, etc.) + bytes[] functionArguments; +} + +struct SolanaInstructionDataDescription { + // flags for accounts, we only need isWritable for now + // 0 bit - isWritable (0|1) + bytes1[] accountFlags; + // names for function argument types used later in data decoding in watcher and transmitter + string[] functionArgumentTypeNames; +} \ No newline at end of file diff --git a/script/super-token-solana/DeployEVMSolanaApps.s.sol b/script/super-token-solana/DeployEVMSolanaApps.s.sol new file mode 100644 index 00000000..de7fd65f --- /dev/null +++ b/script/super-token-solana/DeployEVMSolanaApps.s.sol @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {EvmSolanaAppGateway} from "../../test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol"; +import {SuperTokenAppGateway} from "../../test/apps/app-gateways/super-token/SuperTokenAppGateway.sol"; +import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; +import {ForwarderSolana} from "../../contracts/evmx/helpers/ForwarderSolana.sol"; +import {AddressResolver} from "../../contracts/evmx/helpers/AddressResolver.sol"; + + +// source .env && forge script script/counter/deployEVMxCounterApp.s.sol --broadcast --skip-simulation --legacy --gas-price 0 +contract DeployEVMSolanaApps is Script { + function run() external { + address addressResolver = vm.envAddress("ADDRESS_RESOLVER"); + // address owner = vm.envAddress("OWNER"); + address owner = vm.envAddress("SENDER_ADDRESS"); // TODO: what address should be used here?– + string memory rpc = vm.envString("EVMX_RPC"); + vm.createSelectFork(rpc); + + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + // fill with correct values after deployment + bytes32 solanaProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); + address forwarderSolanaAddress = 0xCEC0c6c88217c687FcdE60bccfc7c14c1c0c72eB; + + // Setting fee payment on Arbitrum Sepolia + uint256 fees = 10 ether; + + EvmSolanaAppGateway gateway = new EvmSolanaAppGateway( + addressResolver, + owner, + fees, + EvmSolanaAppGateway.SuperTokenEvmConstructorParams({ + name_: "SuperToken-Evm", + symbol_: "SUPER", + decimals_: 6, + initialSupplyHolder_: owner, + initialSupply_: 100000000000 + }), + solanaProgramId, + forwarderSolanaAddress + ); + + // TODO: deploy super token on evm + // TODO: callSolana() on gateway + + console.log("Contracts deployed:"); + console.log("EvmSolanaAppGateway:", address(gateway)); + console.log("solanaProgramId:"); + console.logBytes32(solanaProgramId); + console.log("forwarderSolanaAddress:"); + console.logAddress(forwarderSolanaAddress); + + console.log("Forwarder Solana address resolver:"); + console.log(address(ForwarderSolana(forwarderSolanaAddress).addressResolver__())); + console.log("ForwarderSolana chain slug:"); + console.log(ForwarderSolana(forwarderSolanaAddress).chainSlug()); + console.log("ForwarderSolana onChainAddress:"); + console.logBytes32(ForwarderSolana(forwarderSolanaAddress).onChainAddress()); + + console.log("Address resolver from vars:"); + console.log(addressResolver); + + // console.log("Address resolver owner:"); + // console.log(AddressResolver(address(ForwarderSolana(forwarderSolanaAddress).addressResolver__())).owner()); + } +} diff --git a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol new file mode 100644 index 00000000..c8a8bcb2 --- /dev/null +++ b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; +import {EvmSolanaAppGateway} from "../../test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol"; +import {SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../contracts/utils/common/Structs.sol"; + +// source .env && forge script script/counter/EvmSolanaOnchainCalls.s.sol --broadcast --skip-simulation --legacy --gas-price 0 +contract EvmSolanaOnchainCalls is Script { + function run() external { + string memory rpc = vm.envString("EVMX_RPC"); + console.log(rpc); + vm.createSelectFork(rpc); + + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + EvmSolanaAppGateway appGateway = EvmSolanaAppGateway(vm.envAddress("APP_GATEWAY")); + bytes32 switchboardSolana = vm.envBytes32("SWITCHBOARD_SOLANA"); + address userEvmAddress = vm.envAddress("EVM_TEST_ACCOUNT"); + + console.log("EvmSolanaAppGateway:", address(appGateway)); + console.log("Switchboard solana:"); + console.logBytes32(switchboardSolana); + console.log("User address: ", userEvmAddress); + + // console.log("Deploying SuperToken on Optimism Sepolia..."); + // appGateway.deployEvmContract(11155420); + + // appGateway.transfer( + // abi.encode( + // EvmSolanaAppGateway.TransferOrderEvmToSolana({ + // srcEvmToken: 0x4200000000000000000000000000000000000006, + // dstSolanaToken: 0x66619ffe200970bf084fa4713da27d7dff551179adac93fc552787c7555f3482, + // userEvm: 0x4200000000000000000000000000000000000005, + // destUserTokenAddress: 0x44419ffe200970bf084fa4713da27d7dff551179adac93fc552787c7555f3482, + // srcAmount: 1000000000000000000, + // deadline: 1715702400 + // }) + // ), + // switchboardSolana + // ); + + uint256 srcAmount = 1000000; + //mintOnEvm(srcAmount, userEvmAddress, appGateway); + // mintOnSolana(srcAmount, userEvmAddress, switchboardSolana, appGateway); + transferEvmToSolana(srcAmount, userEvmAddress, switchboardSolana, appGateway); + + // This works: + // appGateway.transferForDebug( + // buildSolanaInstruction( + // EvmSolanaAppGateway.TransferOrderEvmToSolana({ + // srcEvmToken: 0x4200000000000000000000000000000000000006, + // // mint on local-testnet: BdUzPsaAicEWinR7b14YLtvavwM8zYn8BaHKqGQ8by2q + // dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, + // userEvm: 0x4200000000000000000000000000000000000005, + // // alice super token ata: LVuCmGaoHjAGu54dFppzujS1Ti61CBac57taeQbokUr + // destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, + // srcAmount: 1000000, + // deadline: 1715702400 + // }) + // ), + // switchboardSolana + // ); + + + } + + function transferEvmToSolana( + uint256 srcAmount, + address userEvmAddress, + bytes32 switchboardSolana, + EvmSolanaAppGateway appGateway + ) public { + console.log("Transfer EVM to Solana"); + + EvmSolanaAppGateway.TransferOrderEvmToSolana memory order = EvmSolanaAppGateway.TransferOrderEvmToSolana({ + srcEvmToken: 0xd912E5870212FC22D56f3C7Cc04aAFfa54eFde31, // Forwarder(!!) for Super-token contract on given chain + // mint on local-testnet: BdUzPsaAicEWinR7b14YLtvavwM8zYn8BaHKqGQ8by2q + dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, + userEvm: userEvmAddress, + // alice super token ata: LVuCmGaoHjAGu54dFppzujS1Ti61CBac57taeQbokUr + destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, + srcAmount: srcAmount, + deadline: 1715702400 + }); + + SolanaInstruction memory solanaInstruction = buildSolanaInstruction(order); + + bytes memory orderEncoded = abi.encode(order); + + appGateway.transfer(orderEncoded, solanaInstruction, switchboardSolana); + } + + function mintOnEvm( + uint256 srcAmount, + address userEvmAddress, + EvmSolanaAppGateway appGateway + ) public { + console.log("Mint on EVM"); + + bytes memory order = abi.encode(EvmSolanaAppGateway.TransferOrderEvmToSolana({ + srcEvmToken: 0xd912E5870212FC22D56f3C7Cc04aAFfa54eFde31, // Forwarder(!!) for Super-token contract on given chain + dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, // irrelevant for EVM minting + userEvm: userEvmAddress, + destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, // irrelevant for EVM minting + srcAmount: srcAmount, + deadline: 1715702400 + })); + + EvmSolanaAppGateway.TransferOrderEvmToSolana memory orderObj = abi.decode(order, (EvmSolanaAppGateway.TransferOrderEvmToSolana)); + console.log("Order srcEvmToken:", orderObj.srcEvmToken); + console.log("Order userEvm:", orderObj.userEvm); + console.log("Order srcAmount:", orderObj.srcAmount); + + appGateway.mintSuperTokenEvm(order); + } + + function mintOnSolana( + uint256 srcAmount, + address userEvmAddress, + bytes32 switchboardSolana, + EvmSolanaAppGateway appGateway + ) public { + console.log("Mint on Solana"); + + SolanaInstruction memory solanaInstruction = buildSolanaInstruction( + EvmSolanaAppGateway.TransferOrderEvmToSolana({ + srcEvmToken: 0xD4a20b34D0dE11e3382Aaa7E0839844f154B6191, + // mint on local-testnet: BdUzPsaAicEWinR7b14YLtvavwM8zYn8BaHKqGQ8by2q + dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, + userEvm: userEvmAddress, + // alice super token ata: LVuCmGaoHjAGu54dFppzujS1Ti61CBac57taeQbokUr + destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, + srcAmount: srcAmount, + deadline: 1715702400 + }) + ); + + appGateway.mintSuperTokenSolana(solanaInstruction, switchboardSolana); + } + + function buildSolanaInstruction( + EvmSolanaAppGateway.TransferOrderEvmToSolana memory order + ) internal view returns (SolanaInstruction memory) { + bytes32 solanaTargetProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); + + // May be subject to change + bytes32[] memory accounts = new bytes32[](5); + // accounts 0 - superTokenConfigPda : jox6eY2gcjaKneNv96TKpjN7f3Rjcpn9dN9ZLNt3Krs + accounts[0] = 0x0af77affb0a5db632e9bafb98525232515d440861c9942e447c20eefd8883d34; + // accounts 1 - mint account + accounts[1] = order.dstSolanaToken; + // accounts 2 - destination user ata + accounts[2] = order.destUserTokenAddress; + // accounts 3 - system programId: 11111111111111111111111111111111 + accounts[3] = 0x0000000000000000000000000000000000000000000000000000000000000000; + // accounts 4 - token programId: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA + accounts[4] = 0x06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9; + + bytes[] memory functionArguments = new bytes[](1); + // TODO:GW: in watcher and transmitter we might need to convert this value if on Solana mint has different decimals, for now we assume that both are the same + functionArguments[0] = abi.encode(order.srcAmount); + + bytes1[] memory accountFlags = new bytes1[](5); + // superTokenConfigPda is not writable + accountFlags[0] = bytes1(0x00); // false + // mint is writable + accountFlags[1] = bytes1(0x01); // true + // destination user ata is writable + accountFlags[2] = bytes1(0x01); // true + // system programId is not writable + accountFlags[3] = bytes1(0x00); // false + // token programId is not writable + accountFlags[4] = bytes1(0x00); // false + + // mint instruction discriminator + bytes8 instructionDiscriminator = 0x3339e12fb69289a6; + + string[] memory functionArgumentTypeNames = new string[](1); + functionArgumentTypeNames[0] = "u64"; + + return + SolanaInstruction({ + data: SolanaInstructionData({ + programId: solanaTargetProgramId, + instructionDiscriminator: instructionDiscriminator, + accounts: accounts, + functionArguments: functionArguments + }), + description: SolanaInstructionDataDescription({ + accountFlags: accountFlags, + functionArgumentTypeNames: functionArgumentTypeNames + }) + }); + } +} diff --git a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol new file mode 100644 index 00000000..5c4eefff --- /dev/null +++ b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "solady/auth/Ownable.sol"; +import "../../../../contracts/evmx/base/AppGatewayBase.sol"; +import "./ISuperToken.sol"; +import "./SuperToken.sol"; +import {SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../../../contracts/utils/common/Structs.sol"; +import {ForwarderSolana} from "../../../../contracts/evmx/helpers/ForwarderSolana.sol"; + + +contract EvmSolanaAppGateway is AppGatewayBase, Ownable { + bytes32 public superTokenEvm = _createContractId("superTokenEvm"); + // solana program address + bytes32 public solanaProgramId; + ForwarderSolana public forwarderSolana; + + event Transferred(uint40 requestCount); + + struct SuperTokenEvmConstructorParams { + string name_; + string symbol_; + uint8 decimals_; + address initialSupplyHolder_; + uint256 initialSupply_; + } + + struct TransferOrderEvmToSolana { + address srcEvmToken; + bytes32 dstSolanaToken; + address userEvm; + bytes32 destUserTokenAddress; + uint256 srcAmount; + uint256 deadline; + } + + constructor( + address addressResolver_, + address owner_, + uint256 fees_, + SuperTokenEvmConstructorParams memory params_, + bytes32 solanaProgramId_, + address forwarderSolanaAddress_ + ) AppGatewayBase(addressResolver_) { + // for evm we use standard mode with contract deployment using EVMx + creationCodeWithArgs[superTokenEvm] = abi.encodePacked( + type(SuperToken).creationCode, + abi.encode( + params_.name_, + params_.symbol_, + params_.decimals_, + params_.initialSupplyHolder_, + params_.initialSupply_ + ) + ); + // for Solana we just pass the programId(program address) + solanaProgramId = solanaProgramId_; + forwarderSolana = ForwarderSolana(forwarderSolanaAddress_); + + // sets the fees data like max fees, chain and token for all transfers + // they can be updated for each transfer as well + _setMaxFees(fees_); + _initializeOwner(owner_); + } + + function deployEvmContract(uint32 chainSlug_) external async(bytes("")) { + bytes memory initData = abi.encodeWithSelector(SuperToken.setOwner.selector, owner()); + _deploy(superTokenEvm, chainSlug_, IsPlug.YES, initData); + } + + // no need to call this directly, will be called automatically after all contracts are deployed. + // check AppGatewayBase._deploy and AppGatewayBase.onRequestComplete + function initialize(uint32) public pure override { + return; + } + + function getForwarderSolanaAddressResolver() external view returns (address) { + return address(forwarderSolana.addressResolver__()); + } + + function transfer(bytes memory order_, SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external async(bytes("")) { + TransferOrderEvmToSolana memory order = abi.decode(order_, (TransferOrderEvmToSolana)); + ISuperToken(order.srcEvmToken).burn(order.userEvm, order.srcAmount); + + // SolanaInstruction memory solanaInstruction = buildSolanaInstruction(order); + + /// we are directly calling the ForwarderSolana + forwarderSolana.callSolana(solanaInstruction, switchboardSolana); + + emit Transferred(_getCurrentAsyncId()); + } + + function mintSuperTokenEvm(bytes memory order_) external async(bytes("")) { + TransferOrderEvmToSolana memory order = abi.decode(order_, (TransferOrderEvmToSolana)); + ISuperToken(order.srcEvmToken).mint(order.userEvm, order.srcAmount); + + emit Transferred(_getCurrentAsyncId()); + } + + function mintSuperTokenSolana(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external async(bytes("")) { + // we are directly calling the ForwarderSolana + forwarderSolana.callSolana(solanaInstruction, switchboardSolana); + + emit Transferred(_getCurrentAsyncId()); + } + + function transferForDebug(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external async(bytes("")) { + // ISuperToken(order.srcEvmToken).burn(order.userEvm, order.srcAmount); + + // we are directly calling the ForwarderSolana + forwarderSolana.callSolana(solanaInstruction, switchboardSolana); + + emit Transferred(_getCurrentAsyncId()); + } + + /* + function buildSolanaInstruction( + TransferOrderEvmToSolana memory order + ) internal view returns (SolanaInstruction memory) { + // May be subject to change + bytes32[] memory accounts = new bytes32[](5); + // accounts 0 - destination user wallet + accounts[0] = order.destUserTokenAddress; + // accounts 1 - mint account + accounts[1] = order.dstSolanaToken; + // accounts 2 - user ata account for mint // TODO:GW: this is random value + accounts[2] = 0x66619ffe200970bf084fa4713da27d7dff551179adac93fc552787c7555f3482; + // accounts 4 - mint authority account (target program PDA) // TODO:GW: this is random value + accounts[4] = 0xfff2e2d5bdb632266e17b0cdce8b7e3f3a7f1d87c096719f234903b39f84d743; + // accounts 5,6 - system_program, token_program (those are static and will be added by the transmitter while making a call) + + bytes[] memory functionArguments = new bytes[](1); + // TODO:GW: in watcher and transmitter we might need to convert this value if on Solana mint has different decimals, for now we assume that both are the same + functionArguments[0] = abi.encode(order.srcAmount); + + bytes1[] memory accountFlags = new bytes1[](4); + accountFlags[0] = bytes1(0x00); + // mint must be is writable + accountFlags[1] = bytes1(0x01); + // dst token ata must be is writable + accountFlags[2] = bytes1(0x01); + accountFlags[3] = bytes1(0x00); + + // TODO:GW: update when TargetDummy is ready + bytes8 instructionDiscriminator = bytes8(uint64(123)); + + string[] memory functionArgumentTypeNames = new string[](1); + functionArgumentTypeNames[0] = "u64"; + + return + SolanaInstruction({ + data: SolanaInstructionData({ + programId: solanaProgramId, + instructionDiscriminator: instructionDiscriminator, + accounts: accounts, + functionArguments: functionArguments + }), + description: SolanaInstructionDataDescription({ + accountFlags: accountFlags, + functionArgumentTypeNames: functionArgumentTypeNames + }) + }); + } + */ +} From efbe2566dd4d5982b79320f7e9c972ee86763ed2 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Fri, 20 Jun 2025 12:13:35 +0200 Subject: [PATCH 02/18] Add ForwarderSolana and solana gatway and call scripts --- .gitignore | 2 + contracts/evmx/helpers/ForwarderSolana.sol | 107 +++++++----------- .../DeployEVMSolanaApps.s.sol | 1 - .../EvmSolanaOnchainCalls.s.sol | 8 +- .../super-token/EvmSolanaAppGateway.sol | 30 ++--- 5 files changed, 59 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index 99e108e6..ee1c88c5 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,5 @@ deployments/local_verification.json testScript.sh CLAUDE.md + +.idea/ diff --git a/contracts/evmx/helpers/ForwarderSolana.sol b/contracts/evmx/helpers/ForwarderSolana.sol index cb35813b..37f6f2c2 100644 --- a/contracts/evmx/helpers/ForwarderSolana.sol +++ b/contracts/evmx/helpers/ForwarderSolana.sol @@ -23,37 +23,9 @@ 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 Storage -/// @notice Storage contract for the Forwarder contract that contains the state variables -abstract contract ForwarderStorage is IForwarder { - // slots [0-49] reserved for gap - uint256[50] _gap_before; - - // slot 50 - /// @notice chain slug on which the contract is deployed - uint32 public chainSlug; - /// @notice Solana on-chain address associated with this forwarder - bytes32 public onChainAddress; - - // slot 51 - /// @notice caches the latest async promise address for the last call - address public latestAsyncPromise; - - // slot 52 - /// @notice the address of the contract that called the latest async promise - address public latestPromiseCaller; - /// @notice the request count of the latest async promise - uint40 public latestRequestCount; - - // slots [53-102] reserved for gap - // TODO:remove-after-review: - 12 for using bytes32 onChainAddress - is there any mainnet deployment which storage could be affected? - uint256[50] _gap_after; - - // slots 103-154 (51) reserved for addr resolver util -} - /// @title Forwarder Contract /// @notice This contract acts as a forwarder for async calls to the on-chain contracts. contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil { @@ -115,32 +87,20 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil /// @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(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external { - if (address(deliveryHelper__()) == address(0)) { - revert DeliveryHelperNotSet(); + // function callSolana(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external { + function callSolana(SolanaInstruction memory solanaInstruction) external { + if (address(watcher__()) == address(0)) { + revert WatcherNotSet(); } // validates if the async modifier is set - bool isAsyncModifierSet = IAppGateway(msg.sender).isAsyncModifierSet(); - if (!isAsyncModifierSet) revert AsyncModifierNotUsed(); - - // Deploy a new async promise contract. - latestAsyncPromise = addressResolver__.deployAsyncPromiseContract(msg.sender); - - // set the latest promise caller and request count for validating if the future .then call is valid - latestPromiseCaller = msg.sender; - latestRequestCount = watcherPrecompile__().nextRequestCount(); + address msgSender = msg.sender; + bool isAsyncModifierSet = IAppGateway(msgSender).isAsyncModifierSet(); + if (!isAsyncModifierSet) revert AsyncModifierNotSet(); // fetch the override params from app gateway - ( - Read isReadCall, - Parallel isParallelCall, - WriteFinality writeFinality, - uint256 readAt, - uint256 gasLimit, - uint256 value, - bytes32 sbType - ) = IAppGateway(msg.sender).getOverrideParams(); + (OverrideParams memory overrideParams, bytes32 sbType) = IAppGateway(msgSender) + .getOverrideParams(); // TODO:GW: after POC make it work like below // get the switchboard address from the watcher precompile config @@ -149,23 +109,34 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil bytes memory solanaPayload = abi.encode(solanaInstruction); // Queue the call in the middleware. - deliveryHelper__().queue( - QueuePayloadParams({ - chainSlug: chainSlug, - callType: isReadCall == Read.ON ? CallType.READ : CallType.WRITE, - isParallel: isParallelCall, - isPlug: IsPlug.NO, - writeFinality: writeFinality, - asyncPromise: latestAsyncPromise, - switchboard: switchboardSolana, - target: onChainAddress, - appGateway: msg.sender, - gasLimit: gasLimit, - value: value, - readAt: readAt, - payload: solanaPayload, - initCallData: bytes("") - }) - ); + QueueParams memory queueParams; + queueParams.overrideParams = overrideParams; + queueParams.transaction = Transaction({ + chainSlug: chainSlug, + target: onChainAddress, + payload: solanaPayload + }); + queueParams.switchboardType = sbType; + watcher__().queue(queueParams, msgSender); + + // Queue the call in the middleware. + // deliveryHelper__().queue( + // QueuePayloadParams({ + // chainSlug: chainSlug, + // callType: isReadCall == Read.ON ? CallType.READ : CallType.WRITE, + // isParallel: isParallelCall, + // isPlug: IsPlug.NO, + // writeFinality: writeFinality, + // asyncPromise: latestAsyncPromise, + // switchboard: switchboardSolana, + // target: onChainAddress, + // appGateway: msg.sender, + // gasLimit: gasLimit, + // value: value, + // readAt: readAt, + // payload: solanaPayload, + // initCallData: bytes("") + // }) + // ); } } diff --git a/script/super-token-solana/DeployEVMSolanaApps.s.sol b/script/super-token-solana/DeployEVMSolanaApps.s.sol index de7fd65f..c4882133 100644 --- a/script/super-token-solana/DeployEVMSolanaApps.s.sol +++ b/script/super-token-solana/DeployEVMSolanaApps.s.sol @@ -30,7 +30,6 @@ contract DeployEVMSolanaApps is Script { uint256 fees = 10 ether; EvmSolanaAppGateway gateway = new EvmSolanaAppGateway( - addressResolver, owner, fees, EvmSolanaAppGateway.SuperTokenEvmConstructorParams({ diff --git a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol index c8a8bcb2..bb2c5290 100644 --- a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol +++ b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol @@ -46,7 +46,7 @@ contract EvmSolanaOnchainCalls is Script { uint256 srcAmount = 1000000; //mintOnEvm(srcAmount, userEvmAddress, appGateway); // mintOnSolana(srcAmount, userEvmAddress, switchboardSolana, appGateway); - transferEvmToSolana(srcAmount, userEvmAddress, switchboardSolana, appGateway); + transferEvmToSolana(srcAmount, userEvmAddress, appGateway); // This works: // appGateway.transferForDebug( @@ -71,7 +71,6 @@ contract EvmSolanaOnchainCalls is Script { function transferEvmToSolana( uint256 srcAmount, address userEvmAddress, - bytes32 switchboardSolana, EvmSolanaAppGateway appGateway ) public { console.log("Transfer EVM to Solana"); @@ -91,7 +90,7 @@ contract EvmSolanaOnchainCalls is Script { bytes memory orderEncoded = abi.encode(order); - appGateway.transfer(orderEncoded, solanaInstruction, switchboardSolana); + appGateway.transfer(orderEncoded, solanaInstruction); } function mintOnEvm( @@ -121,7 +120,6 @@ contract EvmSolanaOnchainCalls is Script { function mintOnSolana( uint256 srcAmount, address userEvmAddress, - bytes32 switchboardSolana, EvmSolanaAppGateway appGateway ) public { console.log("Mint on Solana"); @@ -139,7 +137,7 @@ contract EvmSolanaOnchainCalls is Script { }) ); - appGateway.mintSuperTokenSolana(solanaInstruction, switchboardSolana); + appGateway.mintSuperTokenSolana(solanaInstruction); } function buildSolanaInstruction( diff --git a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol index 5c4eefff..ce2d4694 100644 --- a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol +++ b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol @@ -35,13 +35,12 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { } constructor( - address addressResolver_, address owner_, uint256 fees_, SuperTokenEvmConstructorParams memory params_, bytes32 solanaProgramId_, address forwarderSolanaAddress_ - ) AppGatewayBase(addressResolver_) { + ) { // for evm we use standard mode with contract deployment using EVMx creationCodeWithArgs[superTokenEvm] = abi.encodePacked( type(SuperToken).creationCode, @@ -63,14 +62,14 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { _initializeOwner(owner_); } - function deployEvmContract(uint32 chainSlug_) external async(bytes("")) { + function deployEvmContract(uint32 chainSlug_) external async { bytes memory initData = abi.encodeWithSelector(SuperToken.setOwner.selector, owner()); _deploy(superTokenEvm, chainSlug_, IsPlug.YES, initData); } // no need to call this directly, will be called automatically after all contracts are deployed. // check AppGatewayBase._deploy and AppGatewayBase.onRequestComplete - function initialize(uint32) public pure override { + function initializeOnChain(uint32) public pure override { return; } @@ -78,39 +77,40 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { return address(forwarderSolana.addressResolver__()); } - function transfer(bytes memory order_, SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external async(bytes("")) { + function transfer(bytes memory order_, SolanaInstruction memory solanaInstruction) external async { TransferOrderEvmToSolana memory order = abi.decode(order_, (TransferOrderEvmToSolana)); ISuperToken(order.srcEvmToken).burn(order.userEvm, order.srcAmount); // SolanaInstruction memory solanaInstruction = buildSolanaInstruction(order); /// we are directly calling the ForwarderSolana - forwarderSolana.callSolana(solanaInstruction, switchboardSolana); + forwarderSolana.callSolana(solanaInstruction); - emit Transferred(_getCurrentAsyncId()); + emit Transferred(_getCurrentRequestCount()); } - function mintSuperTokenEvm(bytes memory order_) external async(bytes("")) { + function mintSuperTokenEvm(bytes memory order_) external async { TransferOrderEvmToSolana memory order = abi.decode(order_, (TransferOrderEvmToSolana)); ISuperToken(order.srcEvmToken).mint(order.userEvm, order.srcAmount); - emit Transferred(_getCurrentAsyncId()); + emit Transferred(_getCurrentRequestCount()); } - function mintSuperTokenSolana(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external async(bytes("")) { + function mintSuperTokenSolana(SolanaInstruction memory solanaInstruction) external async { // we are directly calling the ForwarderSolana - forwarderSolana.callSolana(solanaInstruction, switchboardSolana); + forwarderSolana.callSolana(solanaInstruction); - emit Transferred(_getCurrentAsyncId()); + emit Transferred(_getCurrentRequestCount()); } - function transferForDebug(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external async(bytes("")) { + function transferForDebug(SolanaInstruction memory solanaInstruction) external async { // ISuperToken(order.srcEvmToken).burn(order.userEvm, order.srcAmount); // we are directly calling the ForwarderSolana - forwarderSolana.callSolana(solanaInstruction, switchboardSolana); + forwarderSolana.callSolana(solanaInstruction); + + emit Transferred(_getCurrentRequestCount()); - emit Transferred(_getCurrentAsyncId()); } /* From c543ef931bed2552607b01fd81c661955c813024 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Wed, 25 Jun 2025 10:21:50 +0200 Subject: [PATCH 03/18] refactor: add creating Solana style digest; emit debug events; fix deployDment scripts --- Errors.md | 13 ++ EventTopics.md | 9 + FunctionSignatures.md | 17 ++ contracts/evmx/helpers/ForwarderSolana.sol | 21 +-- contracts/evmx/watcher/Configurations.sol | 10 +- contracts/evmx/watcher/RequestHandler.sol | 28 +++ .../watcher/precompiles/WritePrecompile.sol | 163 ++++++++++++++++-- contracts/utils/common/Constants.sol | 2 +- contracts/utils/common/Structs.sol | 2 +- foundry.toml | 48 +++--- hardhat-scripts/deploy/1.deploy.ts | 28 ++- hardhat-scripts/deploy/3.configureChains.ts | 16 ++ hardhat-scripts/deploy/6.connect.ts | 35 +++- hardhat.config.ts | 4 +- .../DeployEVMSolanaApps.s.sol | 6 +- .../EvmSolanaOnchainCalls.s.sol | 130 +++++++++++--- src/enums.ts | 1 + test/DigestTest.t.sol | 88 ++++++++++ .../super-token/EvmSolanaAppGateway.sol | 11 +- 19 files changed, 537 insertions(+), 95 deletions(-) create mode 100644 test/DigestTest.t.sol diff --git a/Errors.md b/Errors.md index dc1933a7..029af1fb 100644 --- a/Errors.md +++ b/Errors.md @@ -17,6 +17,13 @@ | `PromiseRevertFailed()` | `0x0175b9de` | | `NotLatestPromise()` | `0x39ca95d3` | +## evmx/helpers/ForwarderSolana.sol + +| Error | Signature | +|-------|-----------| +| `InvalidSolanaChainSlug()` | `0xe37803ab` | +| `AddressResolverNotSet()` | `0x6d55276d` | + ## evmx/plugs/ContractFactoryPlug.sol | Error | Signature | @@ -33,6 +40,12 @@ | `InvalidDepositAmount()` | `0xfe9ba5cd` | | `TokenNotWhitelisted(address)` | `0xea3bff2e` | +## evmx/watcher/Configurations.sol + +| Error | Signature | +|-------|-----------| +| `InvalidSwitchboardTest(bytes32,bytes32)` | `0x702f36a1` | + ## evmx/watcher/RequestHandler.sol | Error | Signature | diff --git a/EventTopics.md b/EventTopics.md index 2c5b9a2c..589e474b 100644 --- a/EventTopics.md +++ b/EventTopics.md @@ -133,6 +133,12 @@ | ----- | --------- | ----- | | `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | +## ForwarderSolana + +| Event | Arguments | Topic | +| ----- | --------- | ----- | +| `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | + ## ProxyFactory | Event | Arguments | Topic | @@ -187,6 +193,7 @@ | `PlugAdded` | `(appGatewayId: bytes32, chainSlug: uint32, plug: bytes32)` | `0x3734a2406c5c2f2556c82a0819c51e42a135dd102465cc9856594481ea2f1637` | | `SocketSet` | `(chainSlug: uint32, socket: bytes32)` | `0x3200bf6ad2ab31b9220ed9d2f83089d7a1332f55aaa3825c57510743a315165b` | | `SwitchboardSet` | `(chainSlug: uint32, sbType: bytes32, switchboard: bytes32)` | `0xcdfbfa261040f4dffb03c7d9493f74b575f2ae533bb43fd7b5d5b24ac9d804f4` | +| `VerifyConnectionsSB` | `(switchboard: bytes32, switchboardExpected: bytes32)` | `0xf55cb41249952cf17b38f1473238606bcd5048a5202d544f5d401c24a1208403` | ## PromiseResolver @@ -205,6 +212,7 @@ | `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | | `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | | `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | +| `PayloadIdData` | `(requestCount: uint40, batchCount: uint40, payloadCount: uint40, chainSlug: uint32, switchboard: bytes32, packed: bytes)` | `0x40cd955f96207785cb90a63920257959a7ca0c5f89377eaefbe53ccaa8e4b380` | | `RequestCancelled` | `(requestCount: uint40)` | `0xff191657769be72fc08def44c645014c60d18cb24b9ca05c9a33406a28253245` | | `RequestCompletedWithErrors` | `(requestCount: uint40)` | `0xd8d9915dc14b5a29b66cb263e1ea1e99e60418fc21d97f0fbf09cae1281291e2` | | `RequestSettled` | `(requestCount: uint40, winner: address)` | `0x1234f98acbe1548b214f4528461a5377f1e2349569c04caa59325e488e7d2aa4` | @@ -258,6 +266,7 @@ | ----- | --------- | ----- | | `ChainMaxMsgValueLimitsUpdated` | `(chainSlug: uint32, maxMsgValueLimit: uint256)` | `0x439087d094fe7dacbba3f0c67032041952d8bd58a891e15af10ced28fed0eb91` | | `ContractFactoryPlugSet` | `(chainSlug: uint32, contractFactoryPlug: bytes32)` | `0xfad552a6feb82bef23201b8dce04b2460bff41b00f26fef3d791572cfdab49c2` | +| `DigestWithSourceParams` | `(digest: bytes32, digestParams: tuple)` | `0x093e9b93d5aafe2a01beeb5ad1e80356601b533260651c08eda18b79912f315b` | | `ExpiryTimeSet` | `(expiryTime: uint256)` | `0x07e837e13ad9a34715a6bd45f49bbf12de19f06df79cb0be12b3a7d7f2397fa9` | | `FeesSet` | `(writeFees: uint256)` | `0x3346af6da1932164d501f2ec28f8c5d686db5828a36b77f2da4332d89184fe7b` | | `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | diff --git a/FunctionSignatures.md b/FunctionSignatures.md index e5d5ecd3..def1a84b 100644 --- a/FunctionSignatures.md +++ b/FunctionSignatures.md @@ -297,6 +297,22 @@ | `rescueFunds` | `0x6ccae054` | | `watcher__` | `0x300bb063` | +## ForwarderSolana + +| Function | Signature | +| -------- | --------- | +| `addressResolver__` | `0x6a750469` | +| `asyncDeployer__` | `0x2a39e801` | +| `callSolana` | `0x8af147d3` | +| `chainSlug` | `0xb349ba65` | +| `deployForwarder__` | `0xd4e3b034` | +| `feesManager__` | `0x70568b58` | +| `getChainSlug` | `0x0b8c6568` | +| `getOnChainAddress` | `0x9da48789` | +| `initialize` | `0x148841cb` | +| `onChainAddress` | `0x8bd0b363` | +| `watcher__` | `0x300bb063` | + ## ProxyFactory | Function | Signature | @@ -537,6 +553,7 @@ | `completeOwnershipHandover` | `0xf04e283e` | | `contractFactoryPlugs` | `0x35426631` | | `digestHashes` | `0xd1a862bf` | +| `encodeU64Borsh` | `0xacc1b559` | | `expiryTime` | `0x99bc0aea` | | `getDigest` | `0x91b6288b` | | `getPrecompileFees` | `0xb7a3d04c` | diff --git a/contracts/evmx/helpers/ForwarderSolana.sol b/contracts/evmx/helpers/ForwarderSolana.sol index 37f6f2c2..fbdd83c5 100644 --- a/contracts/evmx/helpers/ForwarderSolana.sol +++ b/contracts/evmx/helpers/ForwarderSolana.sol @@ -1,17 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -// import "./interfaces/IAddressResolver.sol"; -// import "./interfaces/IMiddleware.sol"; -// import "./interfaces/IAppGateway.sol"; -// import "./interfaces/IPromise.sol"; -// import "./interfaces/IForwarder.sol"; -// import {AddressResolverUtil} from "./AddressResolverUtil.sol"; -// import {AsyncModifierNotUsed, NoAsyncPromiseFound, PromiseCallerMismatch, RequestCountMismatch, DeliveryHelperNotSet} from "../utils/common/Errors.sol"; -// import "solady/utils/Initializable.sol"; -// import {SolanaInstruction} from "../utils/common/Structs.sol"; -// import {CHAIN_SLUG_SOLANA_MAINNET, CHAIN_SLUG_SOLANA_DEVNET} from "../utils/common/Constants.sol"; - import "solady/utils/Initializable.sol"; import "./AddressResolverUtil.sol"; import "../interfaces/IAddressResolver.sol"; @@ -23,14 +12,13 @@ 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"; - +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 @@ -89,7 +77,10 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil /// @dev It queues the calls in the middleware and deploys the promise contract // function callSolana(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external { function callSolana(SolanaInstruction memory solanaInstruction) external { - if (address(watcher__()) == address(0)) { + if (address(addressResolver__) == address(0)) { + revert AddressResolverNotSet(); + } + if (address(watcher__()) == address(0)) { revert WatcherNotSet(); } diff --git a/contracts/evmx/watcher/Configurations.sol b/contracts/evmx/watcher/Configurations.sol index dc0b3694..d5fdf34d 100644 --- a/contracts/evmx/watcher/Configurations.sol +++ b/contracts/evmx/watcher/Configurations.sol @@ -14,6 +14,8 @@ abstract contract ConfigurationsStorage is IConfigurations { // slots [0-49] reserved for gap uint256[50] _gap_before; + error InvalidSwitchboardTest(bytes32 sb, bytes32 sbExpected); + // slot 50 /// @notice Maps network and plug to their configuration /// @dev chainSlug => plug => PlugConfig @@ -44,6 +46,9 @@ abstract contract ConfigurationsStorage is IConfigurations { /// @notice Configuration contract for the Watcher Precompile system /// @dev Handles the mapping between networks, plugs, and app gateways for payload execution contract Configurations is ConfigurationsStorage, Initializable, Ownable, WatcherBase { + // TODO:GW: remove after testing Solana + event VerifyConnectionsSB(bytes32 switchboard, bytes32 switchboardExpected); + /// @notice Emitted when a new plug is configured for an app gateway /// @param appGatewayId The id of the app gateway /// @param chainSlug The identifier of the destination network @@ -158,10 +163,13 @@ contract Configurations is ConfigurationsStorage, Initializable, Ownable, Watche bytes32 target_, address appGateway_, bytes32 switchboardType_ + // ) external { ) external view { (bytes32 appGatewayId, bytes32 switchboard) = getPlugConfigs(chainSlug_, target_); if (appGatewayId != toBytes32Format(appGateway_)) revert InvalidGateway(); - if (switchboard != switchboards[chainSlug_][switchboardType_]) revert InvalidSwitchboard(); + // emit VerifyConnectionsSB(switchboard, switchboards[chainSlug_][switchboardType_]); + // if (switchboard != switchboards[chainSlug_][switchboardType_]) revert InvalidSwitchboard(); + if (switchboard != switchboards[chainSlug_][switchboardType_]) revert InvalidSwitchboardTest(switchboard, switchboards[chainSlug_][switchboardType_]); } /** diff --git a/contracts/evmx/watcher/RequestHandler.sol b/contracts/evmx/watcher/RequestHandler.sol index ad1a6b71..d9bd933b 100644 --- a/contracts/evmx/watcher/RequestHandler.sol +++ b/contracts/evmx/watcher/RequestHandler.sol @@ -75,6 +75,16 @@ contract RequestHandler is RequestHandlerStorage, Initializable, Ownable, Addres event RequestCompletedWithErrors(uint40 requestCount); event RequestCancelled(uint40 requestCount); + // TODO: remove after testing Solana + event PayloadIdData( + uint40 requestCount, + uint40 batchCount, + uint40 payloadCount, + uint32 chainSlug, + bytes32 switchboard, + bytes packed + ); + modifier isRequestCancelled(uint40 requestCount_) { if (_requests[requestCount_].requestTrackingParams.isRequestCancelled) revert RequestAlreadyCancelled(); @@ -254,6 +264,24 @@ contract RequestHandler is RequestHandlerStorage, Initializable, Ownable, Addres ); _batchPayloadIds[nextBatchCount].push(payloadId); + // TODO: remove after testing Solana + uint32 chainSlugParam = queuePayloadParam.transaction.chainSlug; + bytes memory packed = abi.encodePacked( + requestCount_, + nextBatchCount, + payloadCount, + chainSlugParam, + switchboard + ); + emit PayloadIdData( + requestCount_, + nextBatchCount, + payloadCount, + chainSlugParam, + switchboard, + packed + ); + // create prev digest hash PayloadParams memory p; p.requestCount = requestCount_; diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index b9cc48df..ef7028a9 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -5,7 +5,7 @@ import "solady/utils/Initializable.sol"; import "solady/auth/Ownable.sol"; import "../../interfaces/IPrecompile.sol"; -import {WRITE, PAYLOAD_SIZE_LIMIT} from "../../../utils/common/Constants.sol"; +import {WRITE, PAYLOAD_SIZE_LIMIT, CHAIN_SLUG_SOLANA_MAINNET, CHAIN_SLUG_SOLANA_DEVNET} from "../../../utils/common/Constants.sol"; import {InvalidIndex, MaxMsgValueLimitExceeded, InvalidPayloadSize} from "../../../utils/common/Errors.sol"; import "../../../utils/RescueFundsLib.sol"; import "../WatcherBase.sol"; @@ -61,6 +61,9 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc PayloadParams payloadParams ); + // TODO: remove after testing Solana + event DigestWithSourceParams(bytes32 digest, DigestParams digestParams); + /// @notice Emitted when a proof is uploaded /// @param payloadId The unique identifier for the request /// @param proof The proof from the watcher @@ -157,13 +160,13 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc ( address appGateway, Transaction memory transaction, - , + , // _writeFinality uint256 gasLimit, uint256 value, - + // bytes32 switchboard ) = abi.decode( payloadParams.precompileData, - (address, Transaction, WriteFinality, uint256, uint256, address) + (address, Transaction, WriteFinality, uint256, uint256, bytes32) ); precompileData = payloadParams.precompileData; @@ -175,26 +178,38 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc payloadParams.batchCount ); - // create digest - DigestParams memory digestParams_ = DigestParams( - configurations__().sockets(transaction.chainSlug), - transmitter_, - payloadParams.payloadId, - deadline, - payloadParams.callType, - gasLimit, - value, - transaction.payload, - transaction.target, - toBytes32Format(appGateway), - prevBatchDigestHash, - bytes("") - ); + // Construct parameters for digest calculation + DigestParams memory digestParams_; + if (_isSolanaChainSlug(transaction.chainSlug)) { + digestParams_ = _createSolanaDigestParams( + payloadParams, + transaction, + appGateway, + transmitter_, + prevBatchDigestHash, + deadline, + gasLimit, + value + ); + } else { + digestParams_ = _createEvmDigestParams( + payloadParams, + transaction, + appGateway, + transmitter_, + prevBatchDigestHash, + deadline, + gasLimit, + value + ); + } // Calculate and store digest from payload parameters bytes32 digest = getDigest(digestParams_); digestHashes[payloadParams.payloadId] = digest; + emit DigestWithSourceParams(digest, digestParams_); + emit WriteProofRequested( transmitter_, digest, @@ -250,6 +265,105 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc ); } + function _createEvmDigestParams( + PayloadParams memory payloadParams_, + Transaction memory transaction_, + address appGateway_, + address transmitter_, + bytes32 prevBatchDigestHash_, + uint256 deadline_, + uint256 gasLimit_, + uint256 value_ + ) internal view returns (DigestParams memory) { + // create digest + // DigestParams memory digestParams_ = DigestParams( + // configurations__().sockets(transaction.chainSlug), + // transmitter_, + // payloadParams.payloadId, + // deadline, + // payloadParams.callType, + // gasLimit, + // value, + // transaction.payload, + // transaction.target, + // toBytes32Format(appGateway), + // prevBatchDigestHash, + // bytes("") + // ); + + return + DigestParams( + configurations__().sockets(transaction_.chainSlug), + transmitter_, + payloadParams_.payloadId, + deadline_, + payloadParams_.callType, + gasLimit_, + value_, + transaction_.payload, + transaction_.target, + toBytes32Format(appGateway_), + prevBatchDigestHash_, + bytes("") + ); + } + + function _createSolanaDigestParams( + PayloadParams memory payloadParams_, + Transaction memory transaction_, + address appGateway_, + address transmitter_, + bytes32 prevBatchDigestHash_, + uint256 deadline_, + uint256 gasLimit_, + uint256 value_ + ) internal view returns (DigestParams memory) { + SolanaInstruction memory instruction = abi.decode( + transaction_.payload, + (SolanaInstruction) + ); + // TODO: this is a problem, function arguments must be packed in a way that is not later touched and that can be used on Solana side in raw Instruction call + // like a call data, so it should be Borsh encoded already here + bytes memory functionArgsPacked; + for (uint256 i = 0; i < instruction.data.functionArguments.length; i++) { + uint256 abiDecodedArg = abi.decode(instruction.data.functionArguments[i], (uint256)); + // silent assumption that all arguments are uint64 to simplify the encoding + uint64 arg = uint64(abiDecodedArg); + bytes8 borshEncodedArg = encodeU64Borsh(arg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } + + bytes memory payloadPacked = abi.encodePacked( + instruction.data.programId, + instruction.data.accounts, + instruction.data.instructionDiscriminator, + functionArgsPacked + ); + + // bytes32 of Solana Socket address : 9vFEQ5e3xf4eo17WttfqmXmnqN3gUicrhFGppmmNwyqV + bytes32 hardcodedSocket = 0x84815e8ca2f6dad7e12902c39a51bc72e13c48139b4fb10025d94e7abea2969c; + return + DigestParams( + // watcherPrecompileConfig__.sockets(params_.payloadHeader.getChainSlug()), // TODO: this does not work, for some reason it returns 0x000.... address + hardcodedSocket, + transmitter_, + payloadParams_.payloadId, + deadline_, + payloadParams_.callType, + gasLimit_, + value_, + payloadPacked, + transaction_.target, + toBytes32Format(appGateway_), + prevBatchDigestHash_, + 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 @@ -305,4 +419,15 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); } + + // Borsh helper functions + function encodeU64Borsh(uint64 v) public pure returns (bytes8) { + return bytes8(swapBytes8(v)); + } + + 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); + } } diff --git a/contracts/utils/common/Constants.sol b/contracts/utils/common/Constants.sol index eecc138a..62b19797 100644 --- a/contracts/utils/common/Constants.sol +++ b/contracts/utils/common/Constants.sol @@ -18,4 +18,4 @@ uint256 constant PAYLOAD_SIZE_LIMIT = 24_500; uint16 constant MAX_COPY_BYTES = 2048; // 2KB uint32 constant CHAIN_SLUG_SOLANA_MAINNET = 10000001; -uint32 constant CHAIN_SLUG_SOLANA_DEVNET = 10000002; +uint32 constant CHAIN_SLUG_SOLANA_DEVNET = 10000002; diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index bbfb46e7..3592a67f 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -230,4 +230,4 @@ struct SolanaInstructionDataDescription { bytes1[] accountFlags; // names for function argument types used later in data decoding in watcher and transmitter string[] functionArgumentTypeNames; -} \ No newline at end of file +} diff --git a/foundry.toml b/foundry.toml index 50557a6f..fc7fae52 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,27 +10,29 @@ evm_version = 'paris' via_ir = false [labels] -0xc9155aef2042DB8B088bDC19BaEb6986e291e077 = "AddressResolver" -0xd17957AF30c1AE7250A113E17F221C739B5da536 = "AddressResolverImpl" -0x2bcB99C7E3d135447410A6671F2Ba99A5F6DAEe5 = "AsyncDeployer" -0xa567410c4F3eaeE53F37aAD43FeEEb1E2bfE3D51 = "AsyncDeployerImpl" -0x9F8CB0b040F41cb53D3Ee165a589Ba311fE090eE = "AuctionManager" -0x034E00F1094F962f42950dEF0401e06940c5AfcC = "AuctionManagerImpl" -0x7f073B55a3c44B8ee60f1d21783517df8262e24f = "Configurations" -0x3B5e67DfdC6faf4C0E5C84fBe5600e26ce760444 = "ConfigurationsImpl" -0x72A9923E9dBf1f7a52694a7D467876F1495C656B = "DeployForwarder" -0xBe014D345a7D76F2682464ceF06a55BfA1D7A716 = "DeployForwarderImpl" -0xE12aF35E2930CBF7a7eD6F34ee6b7f9774f1E013 = "ERC1967Factory" -0x0Cab384393ab43A1E2da59498376EfF7324c87E7 = "FeesManager" -0xD6D008738Dac7391023F5C6A6d620AE1DdD1cA08 = "FeesManagerImpl" +0xEBBCa7Aa182fE5Fa6f891a776582Ce945E4ff43a = "AddressResolver" +0xa4FF2b386291A6DbBb6186A1fff37C235E9AF698 = "AddressResolverImpl" +0xD4E83aaDF6893e1a1C41646c068e5795d8708429 = "AsyncDeployer" +0x4b731545444500093957B3521E1A29b1e9BB989d = "AsyncDeployerImpl" +0xf5Ec4693bC9E3df9C248299C1bF5E97cbfaDb413 = "AuctionManager" +0xdDc5460808D19bce6cA05b1DaC425ee80C0A0086 = "AuctionManagerImpl" +0x48b3A2a434b9fE975E4B79a3C28c37Bc06a28f8b = "Configurations" +0x3A0123540353594D8beB826371D18d1F9EA2f01d = "ConfigurationsImpl" +0x7597271576B01bA949b9162870878192811e9268 = "DeployForwarder" +0xE7F208422F817cd703C83A859A2068bf1fF047B3 = "DeployForwarderImpl" +0xE7e73a8ffcF155BCe71BaB0a3b7c8BB68cfb6Bf0 = "ERC1967Factory" +0x357529E7D3F2fC1448560D095337D1bEaaA51Db4 = "FeesManager" +0x6944dC2Ae8ca915c4eE15b8b437E09aD8eBF2ED9 = "FeesManagerImpl" 0x9De353dD1131aB4e502590D3a1832652FA316268 = "FeesPool" -0x1357c7C53c4EC523c2bDde70F2C7c6cC16cfb111 = "PromiseResolver" -0x833CeaE292709aa55C59A9b59f66b5dDB0B50c62 = "ReadPrecompile" -0x8E07c6Ec1EbBDfEEc17103409F583d950B35b2D7 = "RequestHandler" -0x9dC6Cd010CcC342086d3F077a7cB6df0700E7A20 = "RequestHandlerImpl" -0xfE6E9CD8a8902CC90285B1607545A93F3794a391 = "SchedulePrecompile" -0x829a0A0D00c10075Bd17E731C5087Ceb4ebe7EFb = "Watcher" -0x3EC725ccF07d625648DA2eDCE1e05Fd87B9d9de6 = "WatcherImpl" -0xbB8067A271B93d59c22983fad022B5D342D357A5 = "WritePrecompile" -0x4342F1A730bBaFB966795bD57e7561143c902Dcb = "WritePrecompileImpl" -0xf8B4Ff208327Cd03d3Fd56032CCD9578CF49EEdF = "APP_GATEWAY" +0xe116CcF80015162584C77D1e9D1cbE1109443f91 = "ForwarderSolana" +0x0F471B6023CeE741Ec0287EE178b3A5cA90585b2 = "ForwarderSolanaImpl" +0xB1365F70cF2c9d5858F12c8DfB5ECBb66543538C = "PromiseResolver" +0x055C05c3f7cC24f216d42B30c4B94b343eF62f4e = "ReadPrecompile" +0xD243A5761C30Caf3ECC9305F778Ca111698E1182 = "RequestHandler" +0x8A59c8Ec8279366778f7fC4d8f7Ca111D4CfBD3D = "RequestHandlerImpl" +0x36b13Ae0b6d533d8B98EC6d7C7086Eec11361F4A = "SchedulePrecompile" +0xDF6726Cc8867e6AEd6C20f4696a8B98a46036467 = "Watcher" +0x86a2659B57e393aC5Ea4e8bf97e4b1e595a2f9C2 = "WatcherImpl" +0x4276fDBc8383De3CB6F527a40D2e5E4725Ec8BBb = "WritePrecompile" +0xDd13A39F991A7Dd702a0BEBcB31EB2e54d1625E6 = "WritePrecompileImpl" +0x4530a440dcc32206f901325143132da1eDB8d2E9 = "APP_GATEWAY" diff --git a/hardhat-scripts/deploy/1.deploy.ts b/hardhat-scripts/deploy/1.deploy.ts index f5e19534..566bf22e 100644 --- a/hardhat-scripts/deploy/1.deploy.ts +++ b/hardhat-scripts/deploy/1.deploy.ts @@ -2,7 +2,7 @@ import { config } from "dotenv"; import { Contract, utils, Wallet } from "ethers"; import { formatEther } from "ethers/lib/utils"; import { ethers } from "hardhat"; -import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; +import { ChainAddressesObj, ChainId, ChainSlug, Contracts } from "../../src"; import { AUCTION_END_DELAY_SECONDS, BID_TIMEOUT, @@ -37,6 +37,12 @@ config(); let EVMxOwner: string; +// cT9tVQf8NAwHk849ctDqeLhbN2B6JJi3LfR6GfuN751 - super-token test program id +export const mockForwarderSolanaOnChainAddress32Bytes = Buffer.from( + "0914e65e59622aeeefb7f007aef36df62d4c380895553b0643fcc4383c7c2448", + "hex" +); + const main = async () => { logConfig(); await logBalances(); @@ -249,6 +255,26 @@ const deployEVMxContracts = async () => { deployUtils.addresses[Contracts.SchedulePrecompile] = schedulePrecompile.address; + try { + console.log("AddressResolver address:", addressResolver.address); + + deployUtils = await deployContractWithProxy( + Contracts.ForwarderSolana, + `contracts/evmx/helpers/ForwarderSolana.sol`, + [ + ChainId.SOLANA_DEVNET, + mockForwarderSolanaOnChainAddress32Bytes, + addressResolver.address, + ], + proxyFactory, + deployUtils + ); + const forwarderSolanaAddress = deployUtils.addresses[Contracts.ForwarderSolana]; + console.log("ForwarderSolana Proxy:", forwarderSolanaAddress); + } catch (error) { + console.log("Error deploying ForwarderSolana:", error); + } + deployUtils.addresses.startBlock = (deployUtils.addresses.startBlock ? deployUtils.addresses.startBlock diff --git a/hardhat-scripts/deploy/3.configureChains.ts b/hardhat-scripts/deploy/3.configureChains.ts index a19da8bc..0c6deb80 100644 --- a/hardhat-scripts/deploy/3.configureChains.ts +++ b/hardhat-scripts/deploy/3.configureChains.ts @@ -104,6 +104,22 @@ async function setOnchainContracts( [chain, FAST_SWITCHBOARD_TYPE, toBytes32Format(switchboard)], signer ); + console.log("XXX Setting solana switchboard"); + console.log("FAST_SWITCHBOARD_TYPE: ", FAST_SWITCHBOARD_TYPE); + const solanaSwitchboard = process.env.SWITCHBOARD_SOLANA; + if (!solanaSwitchboard) throw new Error("SWITCHBOARD_SOLANA is not set"); + console.log("solanaSwitchboard as bytes32 reversed: ", Buffer.from(toBytes32Format(solanaSwitchboard)).toString("hex")); + await updateContractSettings( + EVMX_CHAIN_ID, + Contracts.Configurations, + "switchboards", + [ChainSlug.SOLANA_DEVNET, FAST_SWITCHBOARD_TYPE], + solanaSwitchboard, + "setSwitchboard", + [ChainSlug.SOLANA_DEVNET, FAST_SWITCHBOARD_TYPE, toBytes32Format(solanaSwitchboard)], + signer + ); + await updateContractSettings( EVMX_CHAIN_ID, Contracts.Configurations, diff --git a/hardhat-scripts/deploy/6.connect.ts b/hardhat-scripts/deploy/6.connect.ts index f9e9f471..e8b56731 100644 --- a/hardhat-scripts/deploy/6.connect.ts +++ b/hardhat-scripts/deploy/6.connect.ts @@ -1,4 +1,4 @@ -import { Wallet } from "ethers"; +import { ethers, Wallet } from "ethers"; import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; import { chains, EVMX_CHAIN_ID, mode } from "../config"; import { AppGatewayConfig, DeploymentAddresses } from "../constants"; @@ -14,6 +14,7 @@ import { } from "../utils"; import { getWatcherSigner, sendWatcherMultiCallWithNonce } from "../utils/sign"; import { isConfigSetOnEVMx, isConfigSetOnSocket } from "../utils"; +import { mockForwarderSolanaOnChainAddress32Bytes } from "./1.deploy"; const plugs = [Contracts.ContractFactoryPlug, Contracts.FeesPlug]; @@ -150,6 +151,38 @@ export const updateConfigEVMx = async () => { }) ); + //TODO:GW: This is a temporary workaround for th Solana POC + //--- + const appGatewayAddress = process.env.APP_GATEWAY; + if (!appGatewayAddress) throw new Error("APP_GATEWAY is not set"); + const solanaSwitchboard = process.env.SWITCHBOARD_SOLANA!.slice(2); // remove 0x prefix for Buffer from conversion + if (!solanaSwitchboard) throw new Error("SWITCHBOARD_SOLANA is not set"); + + const solanaSwitchboardBytes32 = Buffer.from(solanaSwitchboard, "hex"); + const solanaAppGatewayId = ethers.utils.hexZeroPad(appGatewayAddress, 32); + + console.log("SolanaAppGatewayId: ", solanaAppGatewayId); + console.log( + "SolanaSwitchboardBytes32: ", + solanaSwitchboardBytes32.toString("hex") + ); + + appConfigs.push({ + plugConfig: { + appGatewayId: solanaAppGatewayId, + switchboard: "0x" + solanaSwitchboardBytes32.toString("hex"), + }, + plug: "0x" + mockForwarderSolanaOnChainAddress32Bytes.toString("hex"), + chainSlug: ChainSlug.SOLANA_DEVNET, + }); + // appConfigs.push({ + // plug: "0x" + mockForwarderSolanaOnChainAddress32Bytes.toString("hex"), + // appGatewayId: solanaAppGatewayId, + // switchboard: "0x" + solanaSwitchboardBytes32.toString("hex"), + // chainSlug: ChainSlug.SOLANA_DEVNET, + // }); + //--- + // Update configs if any changes needed if (appConfigs.length > 0) { console.log({ appConfigs }); diff --git a/hardhat.config.ts b/hardhat.config.ts index f9745fe6..20f97d25 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -166,7 +166,7 @@ const config: HardhatUserConfig = { evmVersion: "paris", optimizer: { enabled: true, - runs: 1, + runs: 200, details: { yul: true, yulDetails: { @@ -174,7 +174,7 @@ const config: HardhatUserConfig = { }, }, }, - viaIR: false, + viaIR: true, }, }, }; diff --git a/script/super-token-solana/DeployEVMSolanaApps.s.sol b/script/super-token-solana/DeployEVMSolanaApps.s.sol index c4882133..7b55ecec 100644 --- a/script/super-token-solana/DeployEVMSolanaApps.s.sol +++ b/script/super-token-solana/DeployEVMSolanaApps.s.sol @@ -9,7 +9,6 @@ import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; import {ForwarderSolana} from "../../contracts/evmx/helpers/ForwarderSolana.sol"; import {AddressResolver} from "../../contracts/evmx/helpers/AddressResolver.sol"; - // source .env && forge script script/counter/deployEVMxCounterApp.s.sol --broadcast --skip-simulation --legacy --gas-price 0 contract DeployEVMSolanaApps is Script { function run() external { @@ -24,7 +23,7 @@ contract DeployEVMSolanaApps is Script { // fill with correct values after deployment bytes32 solanaProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); - address forwarderSolanaAddress = 0xCEC0c6c88217c687FcdE60bccfc7c14c1c0c72eB; + address forwarderSolanaAddress = 0xe116CcF80015162584C77D1e9D1cbE1109443f91; // Setting fee payment on Arbitrum Sepolia uint256 fees = 10 ether; @@ -40,7 +39,8 @@ contract DeployEVMSolanaApps is Script { initialSupply_: 100000000000 }), solanaProgramId, - forwarderSolanaAddress + forwarderSolanaAddress, + addressResolver ); // TODO: deploy super token on evm diff --git a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol index bb2c5290..141d1792 100644 --- a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol +++ b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol @@ -43,9 +43,9 @@ contract EvmSolanaOnchainCalls is Script { // switchboardSolana // ); - uint256 srcAmount = 1000000; - //mintOnEvm(srcAmount, userEvmAddress, appGateway); - // mintOnSolana(srcAmount, userEvmAddress, switchboardSolana, appGateway); + uint256 srcAmount = 1000000; + // mintOnEvm(srcAmount, userEvmAddress, appGateway); + // mintOnSolana(srcAmount, userEvmAddress, appGateway); transferEvmToSolana(srcAmount, userEvmAddress, appGateway); // This works: @@ -64,8 +64,6 @@ contract EvmSolanaOnchainCalls is Script { // ), // switchboardSolana // ); - - } function transferEvmToSolana( @@ -75,16 +73,17 @@ contract EvmSolanaOnchainCalls is Script { ) public { console.log("Transfer EVM to Solana"); - EvmSolanaAppGateway.TransferOrderEvmToSolana memory order = EvmSolanaAppGateway.TransferOrderEvmToSolana({ - srcEvmToken: 0xd912E5870212FC22D56f3C7Cc04aAFfa54eFde31, // Forwarder(!!) for Super-token contract on given chain - // mint on local-testnet: BdUzPsaAicEWinR7b14YLtvavwM8zYn8BaHKqGQ8by2q - dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, - userEvm: userEvmAddress, - // alice super token ata: LVuCmGaoHjAGu54dFppzujS1Ti61CBac57taeQbokUr - destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, - srcAmount: srcAmount, - deadline: 1715702400 - }); + EvmSolanaAppGateway.TransferOrderEvmToSolana memory order = EvmSolanaAppGateway + .TransferOrderEvmToSolana({ + srcEvmToken: 0x817fe2ED9c6EE7507C30D1feea417d728546efA1, // Forwarder(!!) for Super-token contract on given chain + // mint on local-testnet: BdUzPsaAicEWinR7b14YLtvavwM8zYn8BaHKqGQ8by2q + dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, + userEvm: userEvmAddress, + // alice super token ata: LVuCmGaoHjAGu54dFppzujS1Ti61CBac57taeQbokUr + destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, + srcAmount: srcAmount, + deadline: 1715702400 + }); SolanaInstruction memory solanaInstruction = buildSolanaInstruction(order); @@ -100,16 +99,21 @@ contract EvmSolanaOnchainCalls is Script { ) public { console.log("Mint on EVM"); - bytes memory order = abi.encode(EvmSolanaAppGateway.TransferOrderEvmToSolana({ - srcEvmToken: 0xd912E5870212FC22D56f3C7Cc04aAFfa54eFde31, // Forwarder(!!) for Super-token contract on given chain - dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, // irrelevant for EVM minting - userEvm: userEvmAddress, - destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, // irrelevant for EVM minting - srcAmount: srcAmount, - deadline: 1715702400 - })); + bytes memory order = abi.encode( + EvmSolanaAppGateway.TransferOrderEvmToSolana({ + srcEvmToken: 0x817fe2ED9c6EE7507C30D1feea417d728546efA1, // Forwarder(!!) for Super-token contract on given chain + dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, // irrelevant for EVM minting + userEvm: userEvmAddress, + destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, // irrelevant for EVM minting + srcAmount: srcAmount, + deadline: 1715702400 + }) + ); - EvmSolanaAppGateway.TransferOrderEvmToSolana memory orderObj = abi.decode(order, (EvmSolanaAppGateway.TransferOrderEvmToSolana)); + EvmSolanaAppGateway.TransferOrderEvmToSolana memory orderObj = abi.decode( + order, + (EvmSolanaAppGateway.TransferOrderEvmToSolana) + ); console.log("Order srcEvmToken:", orderObj.srcEvmToken); console.log("Order userEvm:", orderObj.userEvm); console.log("Order srcAmount:", orderObj.srcAmount); @@ -194,4 +198,82 @@ contract EvmSolanaOnchainCalls is Script { }) }); } + + + function buildSolanaInstructionTest( + EvmSolanaAppGateway.TransferOrderEvmToSolana memory order + ) internal view returns (SolanaInstruction memory) { + bytes32 solanaTargetProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); + + // May be subject to change + bytes32[] memory accounts = new bytes32[](5); + // accounts 0 - superTokenConfigPda : jox6eY2gcjaKneNv96TKpjN7f3Rjcpn9dN9ZLNt3Krs + accounts[0] = 0x0af77affb0a5db632e9bafb98525232515d440861c9942e447c20eefd8883d34; + // accounts 1 - mint account + accounts[1] = order.dstSolanaToken; + // accounts 2 - destination user ata + accounts[2] = order.destUserTokenAddress; + // accounts 3 - system programId: 11111111111111111111111111111111 + accounts[3] = 0x0000000000000000000000000000000000000000000000000000000000000000; + // accounts 4 - token programId: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA + accounts[4] = 0x06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9; + + bytes[] memory functionArguments = new bytes[](1); + functionArguments[0] = abi.encode(order.srcAmount); + uint256[] memory array = new uint256[](100); + functionArguments[1] = abi.encode(array); + ComplexTestStruct memory complexTestStruct = ComplexTestStruct({ + name: "test", + addr: 0x1234567890123456789012345678901234567890123456789012345678901234, + isActive: true, + value: 100 + }); + functionArguments[2] = abi.encode(complexTestStruct); + + + string[] memory functionArgumentTypeNames = new string[](1); + functionArgumentTypeNames[0] = "u64"; + functionArgumentTypeNames[1] = "[u64;100]"; + functionArgumentTypeNames[2] = "{\"ComplexTestStruct\": {\"name\": \"string\",\"addr\": \"[u8;32]\",\"isActive\": \"boolean\",\"value\": \"u64\"}}"; + + bytes1[] memory accountFlags = new bytes1[](5); + // superTokenConfigPda is not writable + accountFlags[0] = bytes1(0x00); // false + // mint is writable + accountFlags[1] = bytes1(0x01); // true + // destination user ata is writable + accountFlags[2] = bytes1(0x01); // true + // system programId is not writable + accountFlags[3] = bytes1(0x00); // false + // token programId is not writable + accountFlags[4] = bytes1(0x00); // false + + // mint instruction discriminator + bytes8 instructionDiscriminator = 0x3339e12fb69289a6; + + + + return + SolanaInstruction({ + data: SolanaInstructionData({ + programId: solanaTargetProgramId, + instructionDiscriminator: instructionDiscriminator, + accounts: accounts, + functionArguments: functionArguments + }), + description: SolanaInstructionDataDescription({ + accountFlags: accountFlags, + functionArgumentTypeNames: functionArgumentTypeNames + }) + }); + } + + struct ComplexTestStruct { + string name; + bytes32 addr; + bool isActive; + uint256 value; + } } + + diff --git a/src/enums.ts b/src/enums.ts index f54d3ba8..2554dc61 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -65,6 +65,7 @@ export enum Contracts { FeesPool = "FeesPool", AsyncDeployer = "AsyncDeployer", DeployForwarder = "DeployForwarder", + ForwarderSolana = "ForwarderSolana", } export enum CallTypeNames { diff --git a/test/DigestTest.t.sol b/test/DigestTest.t.sol new file mode 100644 index 00000000..62e85fbc --- /dev/null +++ b/test/DigestTest.t.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "forge-std/Test.sol"; +import {DigestParams} from "../contracts/utils/common/Structs.sol"; +import "../contracts/utils/common/Constants.sol"; +import "forge-std/console.sol"; + +contract DigestTest is Test { + + function testCallType() pure public { + bytes4 READ = bytes4(keccak256("READ")); + bytes4 WRITE = bytes4(keccak256("WRITE")); + bytes4 SCHEDULE = bytes4(keccak256("SCHEDULE")); + + console.log("READ"); + console.logBytes4(READ); + console.log("WRITE"); + console.logBytes4(WRITE); + console.log("SCHEDULE"); + console.logBytes4(SCHEDULE); + } + + /** + emit DigestWithSourceParams(digest: 0xd64549c2e9bc8c443a5e8a5e375c72258a7131088b6fcd0c3297b40a686195b3, digestParams: DigestParams({ socket: 0x84815e8ca2f6dad7e12902c39a51bc72e13c48139b4fb10025d94e7abea2969c, transmitter: 0x138e9840861C983DC0BB9b3e941FB7C0e9Ade320, payloadId: 0x8c60d67962292aec8829ece076feee3bc37b486f9e6939cf56fa4f6bf25553bd, deadline: 1749307926 [1.749e9], callType: 1, gasLimit: 10000000 [1e7], value: 0, payload: 0x0914e65e59622aeeefb7f007aef36df62d4c380895553b0643fcc4383c7c24480af77affb0a5db632e9bafb98525232515d440861c9942e447c20eefd8883d349ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a93339e12fb69289a640420f0000000000, target: 0x55d893e742d43eafc1e6509eefca9ceb635a39bd3394041d334203ed35720922, appGatewayId: 0x000000000000000000000000751085ca028d2bcfc58cee2514def1ed72c843cd, prevDigestsHash: 0x4cfb2ef587acc8ad0cdb441f5b5e0624f7fef9c2fa084f5e93075cdc54d99d8f })) + */ + function testDigest3() public { + bytes32 expectedDigest = 0xd64549c2e9bc8c443a5e8a5e375c72258a7131088b6fcd0c3297b40a686195b3; + + DigestParams memory inputDigestParams = DigestParams({ + socket: 0x84815e8ca2f6dad7e12902c39a51bc72e13c48139b4fb10025d94e7abea2969c, + transmitter: 0x138e9840861C983DC0BB9b3e941FB7C0e9Ade320, + payloadId: 0x965c0b8c6c5c8dc6f433b34b72ecddcec35b2f36f700f50aed20a40366efa88a, + deadline: 1750681840, + callType: WRITE, + gasLimit: 10000000, + value: 0, + payload: hex"0914e65e59622aeeefb7f007aef36df62d4c380895553b0643fcc4383c7c24480af77affb0a5db632e9bafb98525232515d440861c9942e447c20eefd8883d349ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a93339e12fb69289a640420f0000000000", + // TODO: fix with correct super-token program id + target: 0x0914e65e59622aeeefb7f007aef36df62d4c380895553b0643fcc4383c7c2448, + appGatewayId: 0x0000000000000000000000004530a440dcc32206f901325143132da1edb8d2e9, + // prevDigestsHash: 0x4cfb2ef587acc8ad0cdb441f5b5e0624f7fef9c2fa084f5e93075cdc54d99d8f + prevDigestsHash: 0x0000000000000000000000000000000000000000000000000000000000000000 + }); + + assertEq(uint256(inputDigestParams.callType), uint256(1)); + + bytes memory packedParams = abi.encodePacked( + inputDigestParams.socket, + inputDigestParams.transmitter, + inputDigestParams.payloadId, + inputDigestParams.deadline, + inputDigestParams.callType, + inputDigestParams.gasLimit, + inputDigestParams.value, + inputDigestParams.payload, + inputDigestParams.target, + inputDigestParams.appGatewayId, + inputDigestParams.prevDigestsHash, + bytes("") + ); + console.log("packedParams"); + console.logBytes(packedParams); + + bytes32 actualDigest = getDigest(inputDigestParams); + assertEq(actualDigest, expectedDigest); + } + + // taken from WatcherPrecompileCore.getDigest() + function getDigest(DigestParams memory params_) public pure returns (bytes32 digest) { + digest = keccak256( + abi.encodePacked( + params_.socket, + params_.transmitter, + params_.payloadId, + params_.deadline, + params_.callType, + params_.gasLimit, + params_.value, + params_.payload, + params_.target, + params_.appGatewayId, + params_.prevDigestsHash, + bytes("") + ) + ); + } +} diff --git a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol index ce2d4694..3e679626 100644 --- a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol +++ b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol @@ -8,7 +8,6 @@ import "./SuperToken.sol"; import {SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../../../contracts/utils/common/Structs.sol"; import {ForwarderSolana} from "../../../../contracts/evmx/helpers/ForwarderSolana.sol"; - contract EvmSolanaAppGateway is AppGatewayBase, Ownable { bytes32 public superTokenEvm = _createContractId("superTokenEvm"); // solana program address @@ -39,7 +38,8 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { uint256 fees_, SuperTokenEvmConstructorParams memory params_, bytes32 solanaProgramId_, - address forwarderSolanaAddress_ + address forwarderSolanaAddress_, + address addressResolver_ ) { // for evm we use standard mode with contract deployment using EVMx creationCodeWithArgs[superTokenEvm] = abi.encodePacked( @@ -60,6 +60,7 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { // they can be updated for each transfer as well _setMaxFees(fees_); _initializeOwner(owner_); + _initializeAppGateway(addressResolver_); } function deployEvmContract(uint32 chainSlug_) external async { @@ -77,7 +78,10 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { return address(forwarderSolana.addressResolver__()); } - function transfer(bytes memory order_, SolanaInstruction memory solanaInstruction) external async { + function transfer( + bytes memory order_, + SolanaInstruction memory solanaInstruction + ) external async { TransferOrderEvmToSolana memory order = abi.decode(order_, (TransferOrderEvmToSolana)); ISuperToken(order.srcEvmToken).burn(order.userEvm, order.srcAmount); @@ -110,7 +114,6 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { forwarderSolana.callSolana(solanaInstruction); emit Transferred(_getCurrentRequestCount()); - } /* From 9fc61d3060fab7ddc607339a8d911f547e9a89ec Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Wed, 25 Jun 2025 10:28:09 +0200 Subject: [PATCH 04/18] refactor: WritePrecompile -> handlePayload() -> abi.decode for switchboard From 3afb4c85b12b75443a08c70c43a42281f558144f Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Mon, 30 Jun 2025 14:20:43 +0200 Subject: [PATCH 05/18] Borsh encoding and decoding works with tests; first iteration --- Errors.md | 1 + FunctionSignatures.md | 1 - contracts/evmx/helpers/ForwarderSolana.sol | 24 +- contracts/evmx/watcher/BorshDecdoer.sol | 458 +++++++++++ contracts/evmx/watcher/BorshEncoder.sol | 343 ++++++++ contracts/evmx/watcher/Configurations.sol | 8 +- .../watcher/precompiles/WritePrecompile.sol | 65 +- contracts/utils/common/Constants.sol | 5 + contracts/utils/common/Structs.sol | 34 +- foundry.toml | 50 +- hardhat-scripts/deploy/1.deploy.ts | 3 +- hardhat-scripts/deploy/3.configureChains.ts | 13 +- .../DeployEVMSolanaApps.s.sol | 2 +- .../EvmSolanaOnchainCalls.s.sol | 128 ++- setupInfraContracts.sh | 2 +- test/BorshDecoderTest.t.sol | 775 ++++++++++++++++++ test/BorshEncoderTest.t.sol | 249 ++++++ test/DigestTest.t.sol | 51 +- test/apps/Counter.t.sol | 2 +- test/apps/ParallelCounter.t.sol | 2 +- test/apps/SuperToken.t.sol | 2 +- .../super-token/EvmSolanaAppGateway.sol | 12 +- 22 files changed, 2070 insertions(+), 160 deletions(-) create mode 100644 contracts/evmx/watcher/BorshDecdoer.sol create mode 100644 contracts/evmx/watcher/BorshEncoder.sol create mode 100644 test/BorshDecoderTest.t.sol create mode 100644 test/BorshEncoderTest.t.sol diff --git a/Errors.md b/Errors.md index 029af1fb..f27ddc0a 100644 --- a/Errors.md +++ b/Errors.md @@ -45,6 +45,7 @@ | Error | Signature | |-------|-----------| | `InvalidSwitchboardTest(bytes32,bytes32)` | `0x702f36a1` | +| `InvalidGatewayTest(bytes32,bytes32,bytes32)` | `0xc6ad0fcf` | ## evmx/watcher/RequestHandler.sol diff --git a/FunctionSignatures.md b/FunctionSignatures.md index def1a84b..b6374cbc 100644 --- a/FunctionSignatures.md +++ b/FunctionSignatures.md @@ -553,7 +553,6 @@ | `completeOwnershipHandover` | `0xf04e283e` | | `contractFactoryPlugs` | `0x35426631` | | `digestHashes` | `0xd1a862bf` | -| `encodeU64Borsh` | `0xacc1b559` | | `expiryTime` | `0x99bc0aea` | | `getDigest` | `0x91b6288b` | | `getPrecompileFees` | `0xb7a3d04c` | diff --git a/contracts/evmx/helpers/ForwarderSolana.sol b/contracts/evmx/helpers/ForwarderSolana.sol index fbdd83c5..d86b180e 100644 --- a/contracts/evmx/helpers/ForwarderSolana.sol +++ b/contracts/evmx/helpers/ForwarderSolana.sol @@ -76,7 +76,7 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil /// @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(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external { - function callSolana(SolanaInstruction memory solanaInstruction) external { + function callSolana(bytes memory solanaPayload) external { if (address(addressResolver__) == address(0)) { revert AddressResolverNotSet(); } @@ -97,8 +97,6 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil // get the switchboard address from the watcher precompile config // address switchboard = watcherPrecompileConfig().switchboards(chainSlug, sbType); - bytes memory solanaPayload = abi.encode(solanaInstruction); - // Queue the call in the middleware. QueueParams memory queueParams; queueParams.overrideParams = overrideParams; @@ -109,25 +107,5 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil }); queueParams.switchboardType = sbType; watcher__().queue(queueParams, msgSender); - - // Queue the call in the middleware. - // deliveryHelper__().queue( - // QueuePayloadParams({ - // chainSlug: chainSlug, - // callType: isReadCall == Read.ON ? CallType.READ : CallType.WRITE, - // isParallel: isParallelCall, - // isPlug: IsPlug.NO, - // writeFinality: writeFinality, - // asyncPromise: latestAsyncPromise, - // switchboard: switchboardSolana, - // target: onChainAddress, - // appGateway: msg.sender, - // gasLimit: gasLimit, - // value: value, - // readAt: readAt, - // payload: solanaPayload, - // initCallData: bytes("") - // }) - // ); } } diff --git a/contracts/evmx/watcher/BorshDecdoer.sol b/contracts/evmx/watcher/BorshDecdoer.sol new file mode 100644 index 00000000..bc8f8eb3 --- /dev/null +++ b/contracts/evmx/watcher/BorshDecdoer.sol @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../../utils/common/Structs.sol"; + +library BorshDecoder { + // decodes the borsh schema into abi.encode(value) list of params + // all numeric u8/u16/u32/u64 borsh types are decoded and abi encoded as uint256 + // borsh "String" is decoded and abi encoded as string + // all array/Vec numeric borsh types are decoded and abi encoded as uint256[] + // array/Vec of String borsh type is decoded and encoded string[] + // finally all abi encoded params are returned as bytes[] preserving the same order as in GenericSchema + 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(uint256(value)); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u16"))) { + uint16 value = data.decodeU16(); + decodedParams[i] = abi.encode(uint256(value)); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u32"))) { + uint32 value = data.decodeU32(); + decodedParams[i] = abi.encode(uint256(value)); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u64"))) { + uint64 value = data.decodeU64(); + decodedParams[i] = abi.encode(uint256(value)); + } else if (keccak256(bytes(typeName)) == keccak256(bytes("u128"))) { + uint128 value = data.decodeU128(); + decodedParams[i] = abi.encode(uint256(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 (startsWith(typeName, "[u8;")) { + uint256 length = extractArrayLength(typeName); + uint8[] memory value = decodeUint8Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (startsWith(typeName, "[u16;")) { + uint256 length = extractArrayLength(typeName); + uint16[] memory value = decodeUint16Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (startsWith(typeName, "[u32;")) { + uint256 length = extractArrayLength(typeName); + uint32[] memory value = decodeUint32Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (startsWith(typeName, "[u64;")) { + uint256 length = extractArrayLength(typeName); + uint64[] memory value = decodeUint64Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (startsWith(typeName, "[u128;")) { + uint256 length = extractArrayLength(typeName); + uint128[] memory value = decodeUint128Array(data, length); + decodedParams[i] = abi.encode(value); + } else if (startsWith(typeName, "[String;")) { + uint256 length = 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; + } + + 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 + 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(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"); + } + + 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 swapBytes2(uint16(bytes2(data.read(2)))); + } + + function decodeU32(Data memory data) internal pure returns (uint32) { + return swapBytes4(uint32(bytes4(data.read(4)))); + } + + function decodeU64(Data memory data) internal pure returns (uint64) { + return swapBytes8(uint64(bytes8(data.read(8)))); + } + + function decodeU128(Data memory data) internal pure returns (uint128) { + return swapBytes16(uint128(bytes16(data.read(16)))); + } + + function decodeU256(Data memory data) internal pure returns (uint256) { + return 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 = 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; + } + + + /********* Helper byte-swap functions *********/ + // TODO:GW: move to Utils.sol - can we just like that copy Aurora code ?????? + + 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/BorshEncoder.sol b/contracts/evmx/watcher/BorshEncoder.sol new file mode 100644 index 00000000..fec8fb59 --- /dev/null +++ b/contracts/evmx/watcher/BorshEncoder.sol @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../../utils/common/Structs.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 (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 (startsWith(typeName, "[u8;")) { + uint8[] memory abiDecodedArg = abi.decode(data, (uint8[])); + bytes memory borshEncodedArg = encodeUint8Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (startsWith(typeName, "[u16;")) { + uint16[] memory abiDecodedArg = abi.decode(data, (uint16[])); + bytes memory borshEncodedArg = encodeUint16Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (startsWith(typeName, "[u32;")) { + uint32[] memory abiDecodedArg = abi.decode(data, (uint32[])); + bytes memory borshEncodedArg = encodeUint32Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (startsWith(typeName, "[u64;")) { + uint64[] memory abiDecodedArg = abi.decode(data, (uint64[])); + bytes memory borshEncodedArg = encodeUint64Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (startsWith(typeName, "[u128;")) { + uint128[] memory abiDecodedArg = abi.decode(data, (uint128[])); + bytes memory borshEncodedArg = encodeUint128Array(abiDecodedArg); + functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + } else if (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(swapBytes2(v)); + } + + function encodeU32(uint32 v) internal pure returns (bytes4) { + return bytes4(swapBytes4(v)); + } + + function encodeU64(uint64 v) internal pure returns (bytes8) { + return bytes8(swapBytes8(v)); + } + + function encodeU128(uint128 v) internal pure returns (bytes16) { + return bytes16(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) { + 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) { + bytes memory packed = packUint8Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeUint16Vec(uint16[] memory arr) internal pure returns (bytes memory) { + bytes memory packed = packUint16Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeUint32Vec(uint32[] memory arr) internal pure returns (bytes memory) { + bytes memory packed = packUint32Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeUint64Vec(uint64[] memory arr) internal pure returns (bytes memory) { + bytes memory packed = packUint64Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeUint128Vec(uint128[] memory arr) internal pure returns (bytes memory) { + bytes memory packed = packUint128Array(arr); + return bytes.concat(encodeU32(uint32(arr.length)), packed); + } + + function encodeStringVec(string[] memory arr) internal pure returns (bytes memory) { + 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); + } + + /********* Helper byte-swap functions *********/ + + 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; + } + + /********* 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; + } +} diff --git a/contracts/evmx/watcher/Configurations.sol b/contracts/evmx/watcher/Configurations.sol index d5fdf34d..a6ff7d14 100644 --- a/contracts/evmx/watcher/Configurations.sol +++ b/contracts/evmx/watcher/Configurations.sol @@ -15,6 +15,7 @@ abstract contract ConfigurationsStorage is IConfigurations { uint256[50] _gap_before; error InvalidSwitchboardTest(bytes32 sb, bytes32 sbExpected); + error InvalidGatewayTest(bytes32 appGatewayId, bytes32 appGatewayIdExpected, bytes32 switchboard); // slot 50 /// @notice Maps network and plug to their configuration @@ -163,13 +164,14 @@ contract Configurations is ConfigurationsStorage, Initializable, Ownable, Watche bytes32 target_, address appGateway_, bytes32 switchboardType_ - // ) external { ) external view { (bytes32 appGatewayId, bytes32 switchboard) = getPlugConfigs(chainSlug_, target_); - if (appGatewayId != toBytes32Format(appGateway_)) revert InvalidGateway(); + // if (appGatewayId != toBytes32Format(appGateway_)) revert InvalidGateway(); + if (appGatewayId != toBytes32Format(appGateway_)) revert InvalidGatewayTest(appGatewayId, toBytes32Format(appGateway_), switchboard); // emit VerifyConnectionsSB(switchboard, switchboards[chainSlug_][switchboardType_]); // if (switchboard != switchboards[chainSlug_][switchboardType_]) revert InvalidSwitchboard(); - if (switchboard != switchboards[chainSlug_][switchboardType_]) revert InvalidSwitchboardTest(switchboard, switchboards[chainSlug_][switchboardType_]); + if (switchboard != switchboards[chainSlug_][switchboardType_]) + revert InvalidSwitchboardTest(switchboard, switchboards[chainSlug_][switchboardType_]); } /** diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index ef7028a9..1e236b0c 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -10,6 +10,7 @@ import {InvalidIndex, MaxMsgValueLimitExceeded, InvalidPayloadSize} from "../../ import "../../../utils/RescueFundsLib.sol"; import "../WatcherBase.sol"; import {toBytes32Format} from "../../../utils/common/Converters.sol"; +import "../BorshEncoder.sol"; abstract contract WritePrecompileStorage is IPrecompile { // slots [0-49] reserved for gap @@ -48,6 +49,8 @@ abstract contract WritePrecompileStorage is IPrecompile { /// @title WritePrecompile /// @notice Handles write precompile logic contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, WatcherBase { + // using BorshEncoder for borsh; + /// @notice Emitted when fees are set event FeesSet(uint256 writeFees); event ChainMaxMsgValueLimitsUpdated(uint32 chainSlug, uint256 maxMsgValueLimit); @@ -159,12 +162,13 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc { ( address appGateway, - Transaction memory transaction, - , // _writeFinality + Transaction memory transaction, // _writeFinality + , uint256 gasLimit, uint256 value, - // bytes32 switchboard - ) = abi.decode( + + ) = // bytes32 switchboard + abi.decode( payloadParams.precompileData, (address, Transaction, WriteFinality, uint256, uint256, bytes32) ); @@ -275,22 +279,6 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc uint256 gasLimit_, uint256 value_ ) internal view returns (DigestParams memory) { - // create digest - // DigestParams memory digestParams_ = DigestParams( - // configurations__().sockets(transaction.chainSlug), - // transmitter_, - // payloadParams.payloadId, - // deadline, - // payloadParams.callType, - // gasLimit, - // value, - // transaction.payload, - // transaction.target, - // toBytes32Format(appGateway), - // prevBatchDigestHash, - // bytes("") - // ); - return DigestParams( configurations__().sockets(transaction_.chainSlug), @@ -322,16 +310,15 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc transaction_.payload, (SolanaInstruction) ); - // TODO: this is a problem, function arguments must be packed in a way that is not later touched and that can be used on Solana side in raw Instruction call - // like a call data, so it should be Borsh encoded already here - bytes memory functionArgsPacked; - for (uint256 i = 0; i < instruction.data.functionArguments.length; i++) { - uint256 abiDecodedArg = abi.decode(instruction.data.functionArguments[i], (uint256)); - // silent assumption that all arguments are uint64 to simplify the encoding - uint64 arg = uint64(abiDecodedArg); - bytes8 borshEncodedArg = encodeU64Borsh(arg); - functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); - } + bytes memory functionArgsPacked = BorshEncoder.encodeFunctionArgs(instruction); + + // for (uint256 i = 0; i < instruction.data.functionArguments.length; i++) { + // uint256 abiDecodedArg = abi.decode(instruction.data.functionArguments[i], (uint256)); + // // silent assumption that all arguments are uint64 to simplify the encoding + // uint64 arg = uint64(abiDecodedArg); + // bytes8 borshEncodedArg = BorshEncoder.encodeU64(arg); + // functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); + // } bytes memory payloadPacked = abi.encodePacked( instruction.data.programId, @@ -420,14 +407,14 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); } - // Borsh helper functions - function encodeU64Borsh(uint64 v) public pure returns (bytes8) { - return bytes8(swapBytes8(v)); - } + // // Borsh helper functions + // function encodeU64Borsh(uint64 v) public pure returns (bytes8) { + // return bytes8(swapBytes8(v)); + // } - 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 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); + // } } diff --git a/contracts/utils/common/Constants.sol b/contracts/utils/common/Constants.sol index 62b19797..6634a270 100644 --- a/contracts/utils/common/Constants.sol +++ b/contracts/utils/common/Constants.sol @@ -19,3 +19,8 @@ uint16 constant MAX_COPY_BYTES = 2048; // 2KB uint32 constant CHAIN_SLUG_SOLANA_MAINNET = 10000001; uint32 constant CHAIN_SLUG_SOLANA_DEVNET = 10000002; + +/**** Solana predefined account schema types ****/ + +bytes32 constant TOKEN_ACCOUNT = keccak256("TokenAccount"); +bytes32 constant MINT_ACCOUNT = keccak256("MintAccount"); diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index 3592a67f..821b4502 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -210,6 +210,10 @@ struct RequestParams { bytes onCompleteData; } +/********* Solana payloads *********/ + +/** Solana write payload - SolanaInstruction **/ + struct SolanaInstruction { SolanaInstructionData data; SolanaInstructionDataDescription description; @@ -219,8 +223,6 @@ struct SolanaInstructionData { bytes32 programId; bytes32[] accounts; bytes8 instructionDiscriminator; - // TODO:GW: in one of functionArguments is an array it might need a special handling and encoding - // for now we assume the all functionArguments are simple types (uint256, address, bool, etc.) not complex types (struct, array, etc.) bytes[] functionArguments; } @@ -231,3 +233,31 @@ struct SolanaInstructionDataDescription { // names for function argument types used later in data decoding in watcher and transmitter string[] functionArgumentTypeNames; } + +/** Solana read payload - SolanaReadInstruction **/ + +enum SolanaReadSchemaType { + PREDEFINED, + GENERIC +} + +struct SolanaReadRequest { + bytes32 accountToRead; + SolanaReadSchemaType schemaType; + SolanaReadSchema schema; +} + +struct SolanaReadSchema { + PredefinedSchema predefinedSchema; + GenericSchema genericSchema; +} + +struct PredefinedSchema { + // keccak256("schema-name") + bytes32 nameHash; +} + +struct GenericSchema { + // list of types recognizable by BorshEncoder that we expect to read from Solana account (data model) + string[] valuesTypeNames; +} \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index fc7fae52..75b76193 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,29 +10,29 @@ evm_version = 'paris' via_ir = false [labels] -0xEBBCa7Aa182fE5Fa6f891a776582Ce945E4ff43a = "AddressResolver" -0xa4FF2b386291A6DbBb6186A1fff37C235E9AF698 = "AddressResolverImpl" -0xD4E83aaDF6893e1a1C41646c068e5795d8708429 = "AsyncDeployer" -0x4b731545444500093957B3521E1A29b1e9BB989d = "AsyncDeployerImpl" -0xf5Ec4693bC9E3df9C248299C1bF5E97cbfaDb413 = "AuctionManager" -0xdDc5460808D19bce6cA05b1DaC425ee80C0A0086 = "AuctionManagerImpl" -0x48b3A2a434b9fE975E4B79a3C28c37Bc06a28f8b = "Configurations" -0x3A0123540353594D8beB826371D18d1F9EA2f01d = "ConfigurationsImpl" -0x7597271576B01bA949b9162870878192811e9268 = "DeployForwarder" -0xE7F208422F817cd703C83A859A2068bf1fF047B3 = "DeployForwarderImpl" -0xE7e73a8ffcF155BCe71BaB0a3b7c8BB68cfb6Bf0 = "ERC1967Factory" -0x357529E7D3F2fC1448560D095337D1bEaaA51Db4 = "FeesManager" -0x6944dC2Ae8ca915c4eE15b8b437E09aD8eBF2ED9 = "FeesManagerImpl" +0x10E7a47D76931D6383532C5924ae91De2F540f25 = "AddressResolver" +0x0442aDE746E464387EAEC5f44411aB5E866EC8d2 = "AddressResolverImpl" +0x78b5A6BfC55fC818bd532907c566A48863705C94 = "AsyncDeployer" +0xe3F72951f14d09AA7c44a5b0275472C8572D0Bbe = "AsyncDeployerImpl" +0x39C854C26A339CD567e41902068C829eb9BD93f6 = "AuctionManager" +0x6D820620F087d4B687bb41424B7078CA649a9b26 = "AuctionManagerImpl" +0xF0FA3c9a0d71F3e2BAADF323f6b4F0303C60d69E = "Configurations" +0x3390533482A5f94Ebc4e97CFE4Dc678569857629 = "ConfigurationsImpl" +0xa8cdDce9B5F6C4B1FDefa9e1317C0b3d27ee54b3 = "DeployForwarder" +0x116bcEace2F8aa251bf09cbADF5d186bA5376353 = "DeployForwarderImpl" +0x0d2772e0E5A0F9544e9093E2F57a33B5990c5E49 = "ERC1967Factory" +0xedf1aCca2162532BD03F514B62E1aCA9A22f3387 = "FeesManager" +0xaD1D9175BdcbF91e080364FE385dC8601BBBEA72 = "FeesManagerImpl" 0x9De353dD1131aB4e502590D3a1832652FA316268 = "FeesPool" -0xe116CcF80015162584C77D1e9D1cbE1109443f91 = "ForwarderSolana" -0x0F471B6023CeE741Ec0287EE178b3A5cA90585b2 = "ForwarderSolanaImpl" -0xB1365F70cF2c9d5858F12c8DfB5ECBb66543538C = "PromiseResolver" -0x055C05c3f7cC24f216d42B30c4B94b343eF62f4e = "ReadPrecompile" -0xD243A5761C30Caf3ECC9305F778Ca111698E1182 = "RequestHandler" -0x8A59c8Ec8279366778f7fC4d8f7Ca111D4CfBD3D = "RequestHandlerImpl" -0x36b13Ae0b6d533d8B98EC6d7C7086Eec11361F4A = "SchedulePrecompile" -0xDF6726Cc8867e6AEd6C20f4696a8B98a46036467 = "Watcher" -0x86a2659B57e393aC5Ea4e8bf97e4b1e595a2f9C2 = "WatcherImpl" -0x4276fDBc8383De3CB6F527a40D2e5E4725Ec8BBb = "WritePrecompile" -0xDd13A39F991A7Dd702a0BEBcB31EB2e54d1625E6 = "WritePrecompileImpl" -0x4530a440dcc32206f901325143132da1eDB8d2E9 = "APP_GATEWAY" +0x95991009d86FE1bB06e5352169d6b94D10db09D1 = "ForwarderSolana" +0xb54BFc40ED7716a25AA77540D92120370f6Ed8a0 = "ForwarderSolanaImpl" +0xE7C9211D6939d76243B3E88c55bfB125967ec36d = "PromiseResolver" +0x4cdCB7a3cd0D90E0187701600e63DC32CFA8b143 = "ReadPrecompile" +0xd922fd7D4C073594d344DD365585c552D97Fd6A9 = "RequestHandler" +0x738c91b43D6e9c34E4ccDBf661fd926C225Ca222 = "RequestHandlerImpl" +0xAc95d5d8dEf53218792e720E769c3307CB0D5556 = "SchedulePrecompile" +0x98ff886e4EAB2087Bd10589b7E84B57cC2083932 = "Watcher" +0xd56Ca3ffdD70509cfA78E33D699Ab7dA1bE07f53 = "WatcherImpl" +0x038F81B90D925d4100317D0BBEbF5ea32d70b5aC = "WritePrecompile" +0x3FcD9B95d6Ed738562e79C56669F8BEbDB9CE626 = "WritePrecompileImpl" +0x245011D31A1BeA7a0745bF235B08d730aE2B222F = "APP_GATEWAY" diff --git a/hardhat-scripts/deploy/1.deploy.ts b/hardhat-scripts/deploy/1.deploy.ts index 566bf22e..bc279dc6 100644 --- a/hardhat-scripts/deploy/1.deploy.ts +++ b/hardhat-scripts/deploy/1.deploy.ts @@ -269,7 +269,8 @@ const deployEVMxContracts = async () => { proxyFactory, deployUtils ); - const forwarderSolanaAddress = deployUtils.addresses[Contracts.ForwarderSolana]; + const forwarderSolanaAddress = + deployUtils.addresses[Contracts.ForwarderSolana]; console.log("ForwarderSolana Proxy:", forwarderSolanaAddress); } catch (error) { console.log("Error deploying ForwarderSolana:", error); diff --git a/hardhat-scripts/deploy/3.configureChains.ts b/hardhat-scripts/deploy/3.configureChains.ts index 0c6deb80..b99d158d 100644 --- a/hardhat-scripts/deploy/3.configureChains.ts +++ b/hardhat-scripts/deploy/3.configureChains.ts @@ -108,7 +108,10 @@ async function setOnchainContracts( console.log("FAST_SWITCHBOARD_TYPE: ", FAST_SWITCHBOARD_TYPE); const solanaSwitchboard = process.env.SWITCHBOARD_SOLANA; if (!solanaSwitchboard) throw new Error("SWITCHBOARD_SOLANA is not set"); - console.log("solanaSwitchboard as bytes32 reversed: ", Buffer.from(toBytes32Format(solanaSwitchboard)).toString("hex")); + console.log( + "solanaSwitchboard as bytes32 reversed: ", + Buffer.from(toBytes32Format(solanaSwitchboard)).toString("hex") + ); await updateContractSettings( EVMX_CHAIN_ID, Contracts.Configurations, @@ -116,10 +119,14 @@ async function setOnchainContracts( [ChainSlug.SOLANA_DEVNET, FAST_SWITCHBOARD_TYPE], solanaSwitchboard, "setSwitchboard", - [ChainSlug.SOLANA_DEVNET, FAST_SWITCHBOARD_TYPE, toBytes32Format(solanaSwitchboard)], + [ + ChainSlug.SOLANA_DEVNET, + FAST_SWITCHBOARD_TYPE, + toBytes32Format(solanaSwitchboard), + ], signer ); - + await updateContractSettings( EVMX_CHAIN_ID, Contracts.Configurations, diff --git a/script/super-token-solana/DeployEVMSolanaApps.s.sol b/script/super-token-solana/DeployEVMSolanaApps.s.sol index 7b55ecec..e2c3948f 100644 --- a/script/super-token-solana/DeployEVMSolanaApps.s.sol +++ b/script/super-token-solana/DeployEVMSolanaApps.s.sol @@ -23,7 +23,7 @@ contract DeployEVMSolanaApps is Script { // fill with correct values after deployment bytes32 solanaProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); - address forwarderSolanaAddress = 0xe116CcF80015162584C77D1e9D1cbE1109443f91; + address forwarderSolanaAddress = 0x95991009d86FE1bB06e5352169d6b94D10db09D1; // Setting fee payment on Arbitrum Sepolia uint256 fees = 10 ether; diff --git a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol index 141d1792..e48b09aa 100644 --- a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol +++ b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol @@ -3,9 +3,23 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {ETH_ADDRESS} from "../../contracts/utils/common/Constants.sol"; +import { + ETH_ADDRESS, + TOKEN_ACCOUNT, + MINT_ACCOUNT +} from "../../contracts/utils/common/Constants.sol"; import {EvmSolanaAppGateway} from "../../test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol"; -import {SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../contracts/utils/common/Structs.sol"; +import { + SolanaInstruction, + SolanaInstructionData, + SolanaInstructionDataDescription, + SolanaReadRequest, + SolanaReadSchema, + SolanaReadSchemaType, + PredefinedSchema, + GenericSchema +} from "../../contracts/utils/common/Structs.sol"; + // source .env && forge script script/counter/EvmSolanaOnchainCalls.s.sol --broadcast --skip-simulation --legacy --gas-price 0 contract EvmSolanaOnchainCalls is Script { @@ -47,23 +61,6 @@ contract EvmSolanaOnchainCalls is Script { // mintOnEvm(srcAmount, userEvmAddress, appGateway); // mintOnSolana(srcAmount, userEvmAddress, appGateway); transferEvmToSolana(srcAmount, userEvmAddress, appGateway); - - // This works: - // appGateway.transferForDebug( - // buildSolanaInstruction( - // EvmSolanaAppGateway.TransferOrderEvmToSolana({ - // srcEvmToken: 0x4200000000000000000000000000000000000006, - // // mint on local-testnet: BdUzPsaAicEWinR7b14YLtvavwM8zYn8BaHKqGQ8by2q - // dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, - // userEvm: 0x4200000000000000000000000000000000000005, - // // alice super token ata: LVuCmGaoHjAGu54dFppzujS1Ti61CBac57taeQbokUr - // destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, - // srcAmount: 1000000, - // deadline: 1715702400 - // }) - // ), - // switchboardSolana - // ); } function transferEvmToSolana( @@ -75,7 +72,7 @@ contract EvmSolanaOnchainCalls is Script { EvmSolanaAppGateway.TransferOrderEvmToSolana memory order = EvmSolanaAppGateway .TransferOrderEvmToSolana({ - srcEvmToken: 0x817fe2ED9c6EE7507C30D1feea417d728546efA1, // Forwarder(!!) for Super-token contract on given chain + srcEvmToken: 0x2A159f24E2562E5874550BE4702CAC3eAe288411, // Forwarder(!!) for Super-token contract on given chain // mint on local-testnet: BdUzPsaAicEWinR7b14YLtvavwM8zYn8BaHKqGQ8by2q dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, userEvm: userEvmAddress, @@ -101,7 +98,7 @@ contract EvmSolanaOnchainCalls is Script { bytes memory order = abi.encode( EvmSolanaAppGateway.TransferOrderEvmToSolana({ - srcEvmToken: 0x817fe2ED9c6EE7507C30D1feea417d728546efA1, // Forwarder(!!) for Super-token contract on given chain + srcEvmToken: 0x2A159f24E2562E5874550BE4702CAC3eAe288411, // Forwarder(!!) for Super-token contract on given chain dstSolanaToken: 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a, // irrelevant for EVM minting userEvm: userEvmAddress, destUserTokenAddress: 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb, // irrelevant for EVM minting @@ -144,6 +141,52 @@ contract EvmSolanaOnchainCalls is Script { appGateway.mintSuperTokenSolana(solanaInstruction); } + function readSolanaTokenAccount(EvmSolanaAppGateway appGateway) public { + console.log("Read token account from Solana"); + + // put here token account address to be read + bytes32 accountToRead = 0x0000000000000000000000000000000000000000000000000000000000000000; + bytes32 schemaNameHash = TOKEN_ACCOUNT; + + SolanaReadRequest memory readRequest = buildSolanaReadRequestPredefined(accountToRead, schemaNameHash); + + appGateway.readAccount(abi.encode(readRequest)); + } + + function readSolanaSuperTokenConfigAccount(EvmSolanaAppGateway appGateway) public { + console.log("Read generic account from Solana"); + + // put here super-token config account address to be read (PDA taken from Solana) + bytes32 accountToRead = 0x0000000000000000000000000000000000000000000000000000000000000000; + /** Solana super-token config schema: + pub struct Config { + pub owner: [u8;32], + pub chain_slug: u32, + #[max_len(10)] + pub version: String, + pub bump: u8 + } + */ + // TODO:GW: All types recognizable by BorshEncoder must be placed in the constants to avoid hardcoding and confusion with lower/upper case + string[] memory valuesTypeNames = new string[](4); + valuesTypeNames[0] = "[u8;32]"; + valuesTypeNames[1] = "u32"; + valuesTypeNames[2] = "String"; + valuesTypeNames[3] = "u8"; + + SolanaReadRequest memory readRequest = buildSolanaReadRequestGeneric(accountToRead, valuesTypeNames); + // TODO: + // - what happens next how to I get my data back? + // - write borsh decoder (Solidity) for simple types and arrays - used in AppGateway to decode data + // - write borsh decoder (TS) for simple types and arrays - used in watcher to read that data ??? + // - maybe not, maybe watcher just gets the data (in generic case) and this data is decoded on AppGateway ? + // - some decoding need on watcher for Token or Mint accounts which will be predefined + + appGateway.readAccount(abi.encode(readRequest)); + } + + /*************** builder functions ***************/ + function buildSolanaInstruction( EvmSolanaAppGateway.TransferOrderEvmToSolana memory order ) internal view returns (SolanaInstruction memory) { @@ -199,6 +242,40 @@ contract EvmSolanaOnchainCalls is Script { }); } + function buildSolanaReadRequestPredefined(bytes32 accountToRead, bytes32 schemaNameHash) internal view returns (SolanaReadRequest memory) { + SolanaReadRequest memory readRequest = SolanaReadRequest({ + schemaType: SolanaReadSchemaType.PREDEFINED, + accountToRead: accountToRead, + schema: SolanaReadSchema({ + predefinedSchema: PredefinedSchema({ + nameHash: schemaNameHash + }), + genericSchema: GenericSchema({ + valuesTypeNames: new string[](0) + }) + }) + }); + return readRequest; + } + + function buildSolanaReadRequestGeneric(bytes32 accountToRead, string[] memory valuesTypeNames) internal view returns (SolanaReadRequest memory) { + SolanaReadRequest memory readRequest = SolanaReadRequest({ + schemaType: SolanaReadSchemaType.GENERIC, + accountToRead: accountToRead, + schema: SolanaReadSchema({ + predefinedSchema: PredefinedSchema({ + nameHash: bytes32(0) + }), + genericSchema: GenericSchema({ + valuesTypeNames: valuesTypeNames + }) + }) + }); + return readRequest; + } + + + /*************** experimental / testing ***************/ function buildSolanaInstructionTest( EvmSolanaAppGateway.TransferOrderEvmToSolana memory order @@ -230,11 +307,12 @@ contract EvmSolanaOnchainCalls is Script { }); functionArguments[2] = abi.encode(complexTestStruct); - string[] memory functionArgumentTypeNames = new string[](1); functionArgumentTypeNames[0] = "u64"; functionArgumentTypeNames[1] = "[u64;100]"; - functionArgumentTypeNames[2] = "{\"ComplexTestStruct\": {\"name\": \"string\",\"addr\": \"[u8;32]\",\"isActive\": \"boolean\",\"value\": \"u64\"}}"; + functionArgumentTypeNames[ + 2 + ] = '{"ComplexTestStruct": {"name": "string","addr": "[u8;32]","isActive": "boolean","value": "u64"}}'; bytes1[] memory accountFlags = new bytes1[](5); // superTokenConfigPda is not writable @@ -251,8 +329,6 @@ contract EvmSolanaOnchainCalls is Script { // mint instruction discriminator bytes8 instructionDiscriminator = 0x3339e12fb69289a6; - - return SolanaInstruction({ data: SolanaInstructionData({ @@ -275,5 +351,3 @@ contract EvmSolanaOnchainCalls is Script { uint256 value; } } - - diff --git a/setupInfraContracts.sh b/setupInfraContracts.sh index c08fed42..57b29e44 100644 --- a/setupInfraContracts.sh +++ b/setupInfraContracts.sh @@ -16,4 +16,4 @@ time npx hardhat run hardhat-scripts/misc-scripts/eventTopics.ts --no-compile time npx hardhat run hardhat-scripts/misc-scripts/functionSigs.ts --no-compile time npx ts-node hardhat-scripts/misc-scripts/createLabels.ts time npx hardhat run hardhat-scripts/verify/verify.ts --no-compile -yarn lint \ No newline at end of file +# yarn lint \ No newline at end of file diff --git a/test/BorshDecoderTest.t.sol b/test/BorshDecoderTest.t.sol new file mode 100644 index 00000000..774238d2 --- /dev/null +++ b/test/BorshDecoderTest.t.sol @@ -0,0 +1,775 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "forge-std/Test.sol"; +import {BorshEncoder} from "../contracts/evmx/watcher/BorshEncoder.sol"; +import {BorshDecoder} from "../contracts/evmx/watcher/BorshDecdoer.sol"; +import "../contracts/utils/common/Structs.sol"; +import "forge-std/console.sol"; + +contract BorshDecoderTest is Test { + using BorshDecoder for BorshDecoder.Data; + + /** Test primitive type decoding **/ + + function testDecodeU8() public pure { + uint8 originalValue = 42; + bytes1 encoded = BorshEncoder.encodeU8(originalValue); + + BorshDecoder.Data memory data = BorshDecoder.from(abi.encodePacked(encoded)); + uint8 decoded = data.decodeU8(); + + assertEq(decoded, originalValue); + } + + function testDecodeU16() public pure { + uint16 originalValue = 0x1234; + bytes2 encoded = BorshEncoder.encodeU16(originalValue); + + BorshDecoder.Data memory data = BorshDecoder.from(abi.encodePacked(encoded)); + uint16 decoded = data.decodeU16(); + + assertEq(decoded, originalValue); + } + + function testDecodeU32() public pure { + uint32 originalValue = 0x12345678; + bytes4 encoded = BorshEncoder.encodeU32(originalValue); + + BorshDecoder.Data memory data = BorshDecoder.from(abi.encodePacked(encoded)); + uint32 decoded = data.decodeU32(); + + assertEq(decoded, originalValue); + } + + function testDecodeU64() public pure { + uint64 originalValue = 0x123456789abcdef0; + bytes8 encoded = BorshEncoder.encodeU64(originalValue); + + BorshDecoder.Data memory data = BorshDecoder.from(abi.encodePacked(encoded)); + uint64 decoded = data.decodeU64(); + + assertEq(decoded, originalValue); + } + + function testDecodeU128() public pure { + uint128 originalValue = 0x123456789abcdef0fedcba9876543210; + bytes16 encoded = BorshEncoder.encodeU128(originalValue); + + BorshDecoder.Data memory data = BorshDecoder.from(abi.encodePacked(encoded)); + uint128 decoded = data.decodeU128(); + + assertEq(decoded, originalValue); + } + + function testDecodeString() public pure { + string memory originalValue = "hello world"; + bytes memory encoded = BorshEncoder.encodeString(originalValue); + + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + string memory decoded = data.decodeString(); + + assertEq(decoded, originalValue); + } + + function testDecodeStringEmpty() public pure { + string memory originalValue = ""; + bytes memory encoded = BorshEncoder.encodeString(originalValue); + + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + string memory decoded = data.decodeString(); + + assertEq(decoded, originalValue); + } + + // function testDecodeStringSpecialChars() public pure { + // string memory originalValue = "Hello World! @#$%^&*()"; + // bytes memory encoded = BorshEncoder.encodeString(originalValue); + + // BorshDecoder.Data memory data = BorshDecoder.from(encoded); + // string memory decoded = data.decodeString(); + + // assertEq(decoded, originalValue); + // } + + /** Test Vector type decoding **/ + + function testDecodeUint8Vec() public pure { + uint8[] memory originalValues = new uint8[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint8Vec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, uint8[] memory decoded) = data.decodeUint8Vec(); + + assertEq(length, originalValues.length); + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint8VecEmpty() public pure { + uint8[] memory originalValues = new uint8[](0); + + bytes memory encoded = BorshEncoder.encodeUint8Vec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, uint8[] memory decoded) = data.decodeUint8Vec(); + + assertEq(length, 0); + assertEq(decoded.length, 0); + } + + function testDecodeUint8VecLarge() public pure { + uint8[] memory originalValues = new uint8[](255); + for (uint256 i = 0; i < 255; i++) { + originalValues[i] = uint8(i); + } + + bytes memory encoded = BorshEncoder.encodeUint8Vec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, uint8[] memory decoded) = data.decodeUint8Vec(); + + assertEq(length, originalValues.length); + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint16Vec() public pure { + uint16[] memory originalValues = new uint16[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint16Vec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, uint16[] memory decoded) = data.decodeUint16Vec(); + + assertEq(length, originalValues.length); + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint32Vec() public pure { + uint32[] memory originalValues = new uint32[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint32Vec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, uint32[] memory decoded) = data.decodeUint32Vec(); + + assertEq(length, originalValues.length); + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint64Vec() public pure { + uint64[] memory originalValues = new uint64[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint64Vec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, uint64[] memory decoded) = data.decodeUint64Vec(); + + assertEq(length, originalValues.length); + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint128Vec() public pure { + uint128[] memory originalValues = new uint128[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint128Vec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, uint128[] memory decoded) = data.decodeUint128Vec(); + + assertEq(length, originalValues.length); + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeStringVec() public pure { + string[] memory originalValues = new string[](3); + originalValues[0] = "hello"; + originalValues[1] = "world"; + originalValues[2] = "test"; + + bytes memory encoded = BorshEncoder.encodeStringVec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, string[] memory decoded) = data.decodeStringVec(); + + assertEq(length, originalValues.length); + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeStringVecEmpty() public pure { + string[] memory originalValues = new string[](0); + + bytes memory encoded = BorshEncoder.encodeStringVec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, string[] memory decoded) = data.decodeStringVec(); + + assertEq(length, 0); + assertEq(decoded.length, 0); + } + + function testDecodeStringVecWithEmptyStrings() public pure { + string[] memory originalValues = new string[](3); + originalValues[0] = ""; + originalValues[1] = "hello"; + originalValues[2] = ""; + + bytes memory encoded = BorshEncoder.encodeStringVec(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + (uint32 length, string[] memory decoded) = data.decodeStringVec(); + + assertEq(length, originalValues.length); + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + /** Test Array type decoding **/ + + function testDecodeUint8Array() public pure { + uint8[] memory originalValues = new uint8[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint8Array(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + uint8[] memory decoded = data.decodeUint8Array(3); + + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint8ArrayEmpty() public pure { + uint8[] memory originalValues = new uint8[](0); + + bytes memory encoded = BorshEncoder.encodeUint8Array(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + uint8[] memory decoded = data.decodeUint8Array(0); + + assertEq(decoded.length, 0); + } + + function testDecodeUint8ArrayLarge() public pure { + uint8[] memory originalValues = new uint8[](100); + for (uint256 i = 0; i < 100; i++) { + originalValues[i] = uint8(i % 256); + } + + bytes memory encoded = BorshEncoder.encodeUint8Array(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + uint8[] memory decoded = data.decodeUint8Array(100); + + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint16Array() public pure { + uint16[] memory originalValues = new uint16[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint16Array(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + uint16[] memory decoded = data.decodeUint16Array(3); + + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint32Array() public pure { + uint32[] memory originalValues = new uint32[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint32Array(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + uint32[] memory decoded = data.decodeUint32Array(3); + + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint64Array() public pure { + uint64[] memory originalValues = new uint64[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint64Array(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + uint64[] memory decoded = data.decodeUint64Array(3); + + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeUint128Array() public pure { + uint128[] memory originalValues = new uint128[](3); + originalValues[0] = 1; + originalValues[1] = 2; + originalValues[2] = 3; + + bytes memory encoded = BorshEncoder.encodeUint128Array(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + uint128[] memory decoded = data.decodeUint128Array(3); + + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeStringArray() public pure { + string[] memory originalValues = new string[](3); + originalValues[0] = "hello"; + originalValues[1] = "world"; + originalValues[2] = "test"; + + bytes memory encoded = BorshEncoder.encodeStringArray(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + string[] memory decoded = data.decodeStringArray(3); + + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + function testDecodeStringArrayEmpty() public pure { + string[] memory originalValues = new string[](0); + + bytes memory encoded = BorshEncoder.encodeStringArray(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + string[] memory decoded = data.decodeStringArray(0); + + assertEq(decoded.length, 0); + } + + function testDecodeStringArrayWithEmptyStrings() public pure { + string[] memory originalValues = new string[](2); + originalValues[0] = ""; + originalValues[1] = "test"; + + bytes memory encoded = BorshEncoder.encodeStringArray(originalValues); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + string[] memory decoded = data.decodeStringArray(2); + + assertEq(decoded.length, originalValues.length); + for (uint256 i = 0; i < decoded.length; i++) { + assertEq(decoded[i], originalValues[i]); + } + } + + /** Test GenericSchema decoding **/ + + function testDecodeGenericSchemaPrimitives() public pure { + GenericSchema memory schema; + schema.valuesTypeNames = new string[](5); + schema.valuesTypeNames[0] = "u8"; + schema.valuesTypeNames[1] = "u16"; + schema.valuesTypeNames[2] = "u32"; + schema.valuesTypeNames[3] = "u64"; + schema.valuesTypeNames[4] = "u128"; + + // Encode test data + bytes memory encodedData = abi.encodePacked( + BorshEncoder.encodeU8(42), + BorshEncoder.encodeU16(1234), + BorshEncoder.encodeU32(0x12345678), + BorshEncoder.encodeU64(0x123456789abcdef0), + BorshEncoder.encodeU128(0x123456789abcdef0fedcba9876543210) + ); + + bytes[] memory decodedParams = BorshDecoder.decodeGenericSchema(schema, encodedData); + + assertEq(decodedParams.length, 5); + + // Check decoded values + uint256 decodedU8 = abi.decode(decodedParams[0], (uint256)); + assertEq(decodedU8, 42); + + uint256 decodedU16 = abi.decode(decodedParams[1], (uint256)); + assertEq(decodedU16, 1234); + + uint256 decodedU32 = abi.decode(decodedParams[2], (uint256)); + assertEq(decodedU32, 0x12345678); + + uint256 decodedU64 = abi.decode(decodedParams[3], (uint256)); + assertEq(decodedU64, 0x123456789abcdef0); + + uint256 decodedU128 = abi.decode(decodedParams[4], (uint256)); + assertEq(decodedU128, 0x123456789abcdef0fedcba9876543210); + } + + function testDecodeGenericSchemaVectors() public pure { + GenericSchema memory schema; + schema.valuesTypeNames = new string[](1); + schema.valuesTypeNames[0] = "Vec"; + + // Prepare test data + uint8[] memory u8Values = new uint8[](3); + u8Values[0] = 1; + u8Values[1] = 2; + u8Values[2] = 3; + + // Encode test data + bytes memory encodedData = BorshEncoder.encodeUint8Vec(u8Values); + + bytes[] memory decodedParams = BorshDecoder.decodeGenericSchema(schema, encodedData); + + assertEq(decodedParams.length, 1); + + // Check decoded u8 vector + uint256[] memory decodedU8Vec = abi.decode(decodedParams[0], (uint256[])); + assertEq(decodedU8Vec.length, 3); + assertEq(decodedU8Vec[0], 1); + assertEq(decodedU8Vec[1], 2); + assertEq(decodedU8Vec[2], 3); + } + + function testDecodeGenericSchemaArrays() public pure { + GenericSchema memory schema; + schema.valuesTypeNames = new string[](2); + schema.valuesTypeNames[0] = "[u8; 3]"; + schema.valuesTypeNames[1] = "[u16; 2]"; + + // Prepare test data + uint8[] memory u8Values = new uint8[](3); + u8Values[0] = 1; + u8Values[1] = 2; + u8Values[2] = 3; + + uint16[] memory u16Values = new uint16[](2); + u16Values[0] = 1000; + u16Values[1] = 2000; + + // console.log("u8Values"); + // console.logBytes(BorshEncoder.encodeUint8Array(u8Values)); + + // console.log("u16Values"); + // console.logBytes(BorshEncoder.encodeUint16Array(u16Values)); + + // Encode test data + bytes memory encodedData = abi.encodePacked( + BorshEncoder.encodeUint8Array(u8Values), + BorshEncoder.encodeUint16Array(u16Values) + ); + + // console.log("encodedData"); + // console.logBytes(encodedData); + + // console.log("decode data"); + + bytes[] memory decodedParams = BorshDecoder.decodeGenericSchema(schema, encodedData); + + assertEq(decodedParams.length, 2); + + // console.log("decodedParams[0]"); + // console.logBytes(decodedParams[0]); + + // Check decoded u8 array + uint256[] memory decodedU8Array = abi.decode(decodedParams[0], (uint256[])); + assertEq(decodedU8Array.length, 3); + assertEq(decodedU8Array[0], 1); + assertEq(decodedU8Array[1], 2); + assertEq(decodedU8Array[2], 3); + + // console.log("decodedParams[1]"); + // console.logBytes(decodedParams[1]); + + // Check decoded u16 array + uint256[] memory decodedU16Array = abi.decode(decodedParams[1], (uint256[])); + assertEq(decodedU16Array.length, 2); + assertEq(decodedU16Array[0], 1000); + assertEq(decodedU16Array[1], 2000); + } + + function testDecodeGenericSchemaComplex() public pure { + GenericSchema memory schema; + schema.valuesTypeNames = new string[](6); + schema.valuesTypeNames[0] = "u8"; + schema.valuesTypeNames[1] = "u32"; + schema.valuesTypeNames[2] = "u64"; + schema.valuesTypeNames[3] = "u64"; + schema.valuesTypeNames[4] = "[u8; 4]"; + schema.valuesTypeNames[5] = "[u32; 10]"; + + // Prepare test data + uint8 u8Value = 42; + uint32 u32Value = 0x12345678; + uint64 u64Value1 = 0x123456789abcdef0; + uint64 u64Value2 = 0xfedcba9876543210; + + uint8[] memory u8Array = new uint8[](4); + u8Array[0] = 10; + u8Array[1] = 20; + u8Array[2] = 30; + u8Array[3] = 40; + + uint32[] memory u32Array = new uint32[](10); + for (uint256 i = 0; i < 10; i++) { + u32Array[i] = uint32(1000 + i * 100); // 1000, 1100, 1200, ..., 1900 + } + + // Encode test data + bytes memory encodedData = abi.encodePacked( + BorshEncoder.encodeU8(u8Value), + BorshEncoder.encodeU32(u32Value), + BorshEncoder.encodeU64(u64Value1), + BorshEncoder.encodeU64(u64Value2), + BorshEncoder.encodeUint8Array(u8Array), + BorshEncoder.encodeUint32Array(u32Array) + ); + + // Decode using GenericSchema + bytes[] memory decodedParams = BorshDecoder.decodeGenericSchema(schema, encodedData); + + assertEq(decodedParams.length, 6); + + // Check decoded u8 + uint256 decodedU8 = abi.decode(decodedParams[0], (uint256)); + assertEq(decodedU8, uint256(u8Value)); + + // Check decoded u32 + uint256 decodedU32 = abi.decode(decodedParams[1], (uint256)); + assertEq(decodedU32, uint256(u32Value)); + + // Check decoded u64 (first) + uint256 decodedU64_1 = abi.decode(decodedParams[2], (uint256)); + assertEq(decodedU64_1, uint256(u64Value1)); + + // Check decoded u64 (second) + uint256 decodedU64_2 = abi.decode(decodedParams[3], (uint256)); + assertEq(decodedU64_2, uint256(u64Value2)); + + // Check decoded u8 array [u8; 4] + uint256[] memory decodedU8Array = abi.decode(decodedParams[4], (uint256[])); + assertEq(decodedU8Array.length, 4); + assertEq(decodedU8Array[0], 10); + assertEq(decodedU8Array[1], 20); + assertEq(decodedU8Array[2], 30); + assertEq(decodedU8Array[3], 40); + + // Check decoded u32 array [u32; 10] + uint256[] memory decodedU32Array = abi.decode(decodedParams[5], (uint256[])); + assertEq(decodedU32Array.length, 10); + for (uint256 i = 0; i < 10; i++) { + assertEq(decodedU32Array[i], uint256(1000 + i * 100)); + } + } + + function testDecodeGenericSchemaWithStrings() public pure { + GenericSchema memory schema; + schema.valuesTypeNames = new string[](4); + schema.valuesTypeNames[0] = "String"; + schema.valuesTypeNames[1] = "u32"; + schema.valuesTypeNames[2] = "Vec"; + schema.valuesTypeNames[3] = "[String; 2]"; + + // Prepare test data + string memory singleString = "hello world"; + uint32 numberValue = 42; + + string[] memory stringVec = new string[](2); + stringVec[0] = "vec1"; + stringVec[1] = "vec2"; + + string[] memory stringArray = new string[](2); + stringArray[0] = "array1"; + stringArray[1] = "array2"; + + // Encode test data + bytes memory encodedData = abi.encodePacked( + BorshEncoder.encodeString(singleString), + BorshEncoder.encodeU32(numberValue), + BorshEncoder.encodeStringVec(stringVec), + BorshEncoder.encodeStringArray(stringArray) + ); + + // Decode using GenericSchema + bytes[] memory decodedParams = BorshDecoder.decodeGenericSchema(schema, encodedData); + + assertEq(decodedParams.length, 4); + + // Check decoded string + string memory decodedString = abi.decode(decodedParams[0], (string)); + assertEq(decodedString, singleString); + + // Check decoded u32 + uint256 decodedU32 = abi.decode(decodedParams[1], (uint256)); + assertEq(decodedU32, uint256(numberValue)); + + // Check decoded string vector + string[] memory decodedStringVec = abi.decode(decodedParams[2], (string[])); + assertEq(decodedStringVec.length, 2); + assertEq(decodedStringVec[0], "vec1"); + assertEq(decodedStringVec[1], "vec2"); + + // Check decoded string array + string[] memory decodedStringArray = abi.decode(decodedParams[3], (string[])); + assertEq(decodedStringArray.length, 2); + assertEq(decodedStringArray[0], "array1"); + assertEq(decodedStringArray[1], "array2"); + } + + function testDecodeSuperTokenConfigGenericSchema() public pure { + GenericSchema memory schema; + schema.valuesTypeNames = new string[](5); + schema.valuesTypeNames[0] = "[u8;8]"; + schema.valuesTypeNames[1] = "[u8;32]"; + schema.valuesTypeNames[2] = "[u8;32]"; + schema.valuesTypeNames[3] = "[u8;32]"; + schema.valuesTypeNames[4] = "u8"; + + bytes8 discriminator = 0x9b0caae01efacc82; + bytes32 owner = 0x0c1a5886fe1093df9fc438c296f9f7275b7718b6bc0e156d8d336c58f083996d; + bytes32 socket = 0x0000000000000000000000000000000000000000000000000000000000000000; + bytes32 mint = 0x9ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a; + uint8 bump = 255; + + bytes memory solanaEncodedData = hex"9b0caae01efacc820c1a5886fe1093df9fc438c296f9f7275b7718b6bc0e156d8d336c58f083996d00000000000000000000000000000000000000000000000000000000000000009ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522aff"; + + bytes[] memory decodedParams = BorshDecoder.decodeGenericSchema(schema, solanaEncodedData); + + assertEq(decodedParams.length, 5); + + console.log("decoded discriminator"); + // console.logBytes(decodedParams[0]); + uint8[] memory decodedDiscriminator = abi.decode(decodedParams[0], (uint8[])); + console.log("decodedDiscriminator"); + console.log(decodedDiscriminator.length); + bytes memory packedUint8Array = BorshEncoder.packUint8Array(decodedDiscriminator); + console.logBytes(packedUint8Array); + assertEq(packedUint8Array, abi.encodePacked(discriminator)); + + console.log("decodedOwner"); + // console.logBytes(decodedParams[1]); + uint8[] memory decodedOwner = abi.decode(decodedParams[1], (uint8[])); + packedUint8Array = BorshEncoder.packUint8Array(decodedOwner); + console.logBytes(packedUint8Array); + assertEq(packedUint8Array, abi.encodePacked(owner)); + console.log("decodedSocket"); + // console.logBytes(decodedParams[2]); + uint8[] memory decodedSocket = abi.decode(decodedParams[2], (uint8[])); + packedUint8Array = BorshEncoder.packUint8Array(decodedSocket); + console.logBytes(packedUint8Array); + assertEq(packedUint8Array, abi.encodePacked(socket)); + console.log("decodedMint"); + // console.logBytes(decodedParams[3]); + uint8[] memory decodedMint = abi.decode(decodedParams[3], (uint8[])); + packedUint8Array = BorshEncoder.packUint8Array(decodedMint); + console.logBytes(packedUint8Array); + assertEq(packedUint8Array, abi.encodePacked(mint)); + console.log("decodedBump"); + // console.logBytes(decodedParams[4]); + uint8 decodedBump = abi.decode(decodedParams[4], (uint8)); + console.log("decodedBump: ", decodedBump); + assertEq(decodedBump, bump); + } + + /** Test edge cases **/ + + function testDecodeInsufficientData() public { + bytes memory shortData = hex"01"; + + // Should revert when trying to decode u16 from 1-byte data + BorshDecoder.Data memory data = BorshDecoder.from(shortData); + vm.expectRevert("Parse error: unexpected EOI"); + data.decodeU16(); + } + + function testDecodeOutOfBounds() public { + bytes memory data = hex"0102"; + + // Should revert when trying to decode u32 from 2-byte data + BorshDecoder.Data memory decoderData = BorshDecoder.from(data); + vm.expectRevert("Parse error: unexpected EOI"); + decoderData.decodeU32(); + } + + function testDecodeVecInsufficientLength() public { + // Length says 10 but only 5 bytes follow + bytes memory invalidVecData = hex"0a000000010203"; + + BorshDecoder.Data memory data = BorshDecoder.from(invalidVecData); + vm.expectRevert("Parse error: unexpected EOI"); + data.decodeUint8Vec(); + } + + /** Test complex scenarios **/ + + function testDecodeMultipleConsecutive() public pure { + // Encode multiple values consecutively + bytes memory data = abi.encodePacked( + BorshEncoder.encodeU8(42), + BorshEncoder.encodeU16(1234), + BorshEncoder.encodeUint8Vec(_createU8Array()) + ); + + // Decode them one by one + BorshDecoder.Data memory decoderData = BorshDecoder.from(data); + + uint8 u8Val = decoderData.decodeU8(); + assertEq(u8Val, 42); + + uint16 u16Val = decoderData.decodeU16(); + assertEq(u16Val, 1234); + + (uint32 length, uint8[] memory vecVal) = decoderData.decodeUint8Vec(); + assertEq(length, 2); + assertEq(vecVal.length, 2); + assertEq(vecVal[0], 1); + assertEq(vecVal[1], 2); + + // Verify all data consumed + decoderData.done(); + } + + function _createU8Array() private pure returns (uint8[] memory) { + uint8[] memory arr = new uint8[](2); + arr[0] = 1; + arr[1] = 2; + return arr; + } +} \ No newline at end of file diff --git a/test/BorshEncoderTest.t.sol b/test/BorshEncoderTest.t.sol new file mode 100644 index 00000000..0c9bb860 --- /dev/null +++ b/test/BorshEncoderTest.t.sol @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "forge-std/Test.sol"; +import "forge-std/console.sol"; +import {BorshEncoder} from "../contracts/evmx/watcher/BorshEncoder.sol"; + +contract BorshEncoderTest is Test { + /** Encode primitive types **/ + + function testEncodeU8() public pure { + uint8 value = 1; + bytes1 encoded = BorshEncoder.encodeU8(value); + console.logBytes1(encoded); + console.logBytes1(bytes1(value)); + assertEq(encoded, bytes1(value)); + } + + function testEncodeU16() public pure { + uint16 value = 0x0102; + bytes2 encoded = BorshEncoder.encodeU16(value); + assertEq(encoded, bytes2(0x0201)); + } + + function testEncodeU32() public pure { + uint32 value = 0x01020304; + bytes4 encoded = BorshEncoder.encodeU32(value); + // console.logBytes4(encoded); + // console.logBytes4(bytes4(value)); + assertEq(encoded, bytes4(0x04030201)); + } + + function testEncodeU64() public pure { + uint64 value = 0x0102030405060708; + bytes8 encoded = BorshEncoder.encodeU64(value); + assertEq(encoded, bytes8(0x0807060504030201)); + } + + function testEncodeU128() public pure { + uint128 value = 0x0102030405060708090a0b0c0d0e0f10; + bytes16 encoded = BorshEncoder.encodeU128(value); + assertEq(encoded, bytes16(0x100f0e0d0c0b0a090807060504030201)); + } + + function testEncodeString() public pure { + string memory value = "hello"; + bytes memory encoded = BorshEncoder.encodeString(value); + console.logBytes(encoded); + console.logBytes(bytes("hello")); + console.logBytes(hex"0500000068656c6c6f"); + // first 4 bytes are the length of the string which is 5 in LE : 0x05000000 + // rest is the string as bytes with no changes + assertEq(encoded, hex"0500000068656c6c6f"); + } + + /** Encode array types with fixed length - no length prefix, just the bytes **/ + + function testEncodeUint8Array() public pure { + bytes memory expectedEncoded = hex"010203"; + + uint8[] memory value = new uint8[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint8Array(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeUint16Array() public pure { + bytes memory expectedEncoded = hex"010002000300"; + + uint16[] memory value = new uint16[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint16Array(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeUint32Array() public pure { + bytes memory expectedEncoded = hex"010000000200000003000000"; + + uint32[] memory value = new uint32[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint32Array(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeUint64Array() public pure { + bytes memory expectedEncoded = hex"010000000000000002000000000000000300000000000000"; + + uint64[] memory value = new uint64[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint64Array(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeUint128Array() public pure { + bytes + memory expectedEncoded = hex"010000000000000000000000000000000200000000000000000000000000000003000000000000000000000000000000"; + + uint128[] memory value = new uint128[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint128Array(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeStringArray() public pure { + // "hello" (5 bytes): 0x0500000068656c6c6f + // "world" (5 bytes): 0x05000000776f726c64 + bytes memory expectedEncoded = hex"0500000068656c6c6f05000000776f726c64"; + + string[] memory value = new string[](2); + value[0] = "hello"; + value[1] = "world"; + bytes memory encoded = BorshEncoder.encodeStringArray(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeStringArrayEmpty() public pure { + // Empty string (0 bytes): 0x00000000 + bytes memory expectedEncoded = hex"0000000000000000"; + + string[] memory value = new string[](2); + value[0] = ""; + value[1] = ""; + bytes memory encoded = BorshEncoder.encodeStringArray(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeStringArraySingleChar() public pure { + bytes memory expectedEncoded = hex"0500000068656c6c6f05000000776f726c64"; + + string[] memory value = new string[](2); + value[0] = "hello"; + value[1] = "world"; + bytes memory encoded = BorshEncoder.encodeStringArray(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + /** Encode Vector types with that can have variable length - length prefix is added **/ + + function testEncodeUint8Vec() public pure { + // Length: 3 as u32 (0x03000000) + elements (0x010203) + bytes memory expectedEncoded = hex"03000000010203"; + + uint8[] memory value = new uint8[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint8Vec(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeUint16Vec() public pure { + // Length: 3 as u32 (0x03000000) + elements (0x010002000300) + bytes memory expectedEncoded = hex"03000000010002000300"; + + uint16[] memory value = new uint16[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint16Vec(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeUint32Vec() public pure { + // Length: 3 as u32 (0x03000000) + elements (0x010000000200000003000000) + bytes memory expectedEncoded = hex"03000000010000000200000003000000"; + + uint32[] memory value = new uint32[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint32Vec(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeUint64Vec() public pure { + // Length: 3 as u32 (0x03000000) + elements (0x010000000000000002000000000000000300000000000000) + bytes + memory expectedEncoded = hex"03000000010000000000000002000000000000000300000000000000"; + + uint64[] memory value = new uint64[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint64Vec(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeUint128Vec() public pure { + // Length: 3 as u32 (0x03000000) + elements + bytes + memory expectedEncoded = hex"03000000010000000000000000000000000000000200000000000000000000000000000003000000000000000000000000000000"; + + uint128[] memory value = new uint128[](3); + value[0] = 1; + value[1] = 2; + value[2] = 3; + bytes memory encoded = BorshEncoder.encodeUint128Vec(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } + + function testEncodeStringVec() public pure { + // Length: 2 as u32 (0x02000000) + string elements + bytes memory expectedEncoded = hex"020000000500000068656c6c6f05000000776f726c64"; + + string[] memory value = new string[](2); + value[0] = "hello"; + value[1] = "world"; + bytes memory encoded = BorshEncoder.encodeStringVec(value); + + console.logBytes(encoded); + assertEq(encoded, expectedEncoded); + } +} diff --git a/test/DigestTest.t.sol b/test/DigestTest.t.sol index 62e85fbc..b7ea0ffc 100644 --- a/test/DigestTest.t.sol +++ b/test/DigestTest.t.sol @@ -7,25 +7,21 @@ import "../contracts/utils/common/Constants.sol"; import "forge-std/console.sol"; contract DigestTest is Test { - - function testCallType() pure public { - bytes4 READ = bytes4(keccak256("READ")); - bytes4 WRITE = bytes4(keccak256("WRITE")); - bytes4 SCHEDULE = bytes4(keccak256("SCHEDULE")); - + function testCallType() public pure { console.log("READ"); console.logBytes4(READ); console.log("WRITE"); console.logBytes4(WRITE); console.log("SCHEDULE"); console.logBytes4(SCHEDULE); + + bytes32 superTokenEvm = keccak256(abi.encode("superTokenEvm")); + console.log("superTokenEvm"); + console.logBytes32(superTokenEvm); } - /** - emit DigestWithSourceParams(digest: 0xd64549c2e9bc8c443a5e8a5e375c72258a7131088b6fcd0c3297b40a686195b3, digestParams: DigestParams({ socket: 0x84815e8ca2f6dad7e12902c39a51bc72e13c48139b4fb10025d94e7abea2969c, transmitter: 0x138e9840861C983DC0BB9b3e941FB7C0e9Ade320, payloadId: 0x8c60d67962292aec8829ece076feee3bc37b486f9e6939cf56fa4f6bf25553bd, deadline: 1749307926 [1.749e9], callType: 1, gasLimit: 10000000 [1e7], value: 0, payload: 0x0914e65e59622aeeefb7f007aef36df62d4c380895553b0643fcc4383c7c24480af77affb0a5db632e9bafb98525232515d440861c9942e447c20eefd8883d349ded6d20f1f5b9c56cb90ef89fc52d355aaaa868c42738eff11f50d1f81f522a04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb000000000000000000000000000000000000000000000000000000000000000006ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a93339e12fb69289a640420f0000000000, target: 0x55d893e742d43eafc1e6509eefca9ceb635a39bd3394041d334203ed35720922, appGatewayId: 0x000000000000000000000000751085ca028d2bcfc58cee2514def1ed72c843cd, prevDigestsHash: 0x4cfb2ef587acc8ad0cdb441f5b5e0624f7fef9c2fa084f5e93075cdc54d99d8f })) - */ - function testDigest3() public { - bytes32 expectedDigest = 0xd64549c2e9bc8c443a5e8a5e375c72258a7131088b6fcd0c3297b40a686195b3; + function testDigest() public pure { + bytes32 expectedDigest = 0xc26b01718c6f97b51ad73743bb5b1ac2abb53966d15a2948f65db43b30cce1a1; DigestParams memory inputDigestParams = DigestParams({ socket: 0x84815e8ca2f6dad7e12902c39a51bc72e13c48139b4fb10025d94e7abea2969c, @@ -40,24 +36,23 @@ contract DigestTest is Test { target: 0x0914e65e59622aeeefb7f007aef36df62d4c380895553b0643fcc4383c7c2448, appGatewayId: 0x0000000000000000000000004530a440dcc32206f901325143132da1edb8d2e9, // prevDigestsHash: 0x4cfb2ef587acc8ad0cdb441f5b5e0624f7fef9c2fa084f5e93075cdc54d99d8f - prevDigestsHash: 0x0000000000000000000000000000000000000000000000000000000000000000 + prevBatchDigestHash: 0x0000000000000000000000000000000000000000000000000000000000000000, + extraData: bytes("") }); - assertEq(uint256(inputDigestParams.callType), uint256(1)); - bytes memory packedParams = abi.encodePacked( - inputDigestParams.socket, - inputDigestParams.transmitter, - inputDigestParams.payloadId, - inputDigestParams.deadline, - inputDigestParams.callType, - inputDigestParams.gasLimit, - inputDigestParams.value, - inputDigestParams.payload, - inputDigestParams.target, - inputDigestParams.appGatewayId, - inputDigestParams.prevDigestsHash, - bytes("") + inputDigestParams.socket, + inputDigestParams.transmitter, + inputDigestParams.payloadId, + inputDigestParams.deadline, + inputDigestParams.callType, + inputDigestParams.gasLimit, + inputDigestParams.value, + inputDigestParams.payload, + inputDigestParams.target, + inputDigestParams.appGatewayId, + inputDigestParams.prevBatchDigestHash, + inputDigestParams.extraData ); console.log("packedParams"); console.logBytes(packedParams); @@ -80,8 +75,8 @@ contract DigestTest is Test { params_.payload, params_.target, params_.appGatewayId, - params_.prevDigestsHash, - bytes("") + params_.prevBatchDigestHash, + params_.extraData ) ); } diff --git a/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index 4d0b5010..3472fe0b 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -90,7 +90,7 @@ contract CounterTest is AppGatewayBaseSetup { counterGateway ); address optCounter = fromBytes32Format(optCounterBytes32); - + uint256 arbCounterBefore = Counter(arbCounter).counter(); uint256 optCounterBefore = Counter(optCounter).counter(); diff --git a/test/apps/ParallelCounter.t.sol b/test/apps/ParallelCounter.t.sol index 3acd413c..51763bb3 100644 --- a/test/apps/ParallelCounter.t.sol +++ b/test/apps/ParallelCounter.t.sol @@ -137,7 +137,7 @@ contract ParallelCounterTest is AppGatewayBaseSetup { parallelCounterGateway ); address arbCounter = fromBytes32Format(arbCounterBytes32); - + (bytes32 optCounterBytes32, address optCounterForwarder) = getOnChainAndForwarderAddresses( optChainSlug, counterId1, diff --git a/test/apps/SuperToken.t.sol b/test/apps/SuperToken.t.sol index 3b35db2d..18a9ea24 100644 --- a/test/apps/SuperToken.t.sol +++ b/test/apps/SuperToken.t.sol @@ -151,7 +151,7 @@ contract SuperTokenTest is AppGatewayBaseSetup { IAppGateway(appContracts.superTokenApp) ); address onChainOpt = fromBytes32Format(onChainOptBytes32); - + uint256 arbBalanceBefore = SuperToken(onChainArb).balanceOf(owner); uint256 optBalanceBefore = SuperToken(onChainOpt).balanceOf(owner); diff --git a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol index 3e679626..6355ef7d 100644 --- a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol +++ b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol @@ -88,7 +88,7 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { // SolanaInstruction memory solanaInstruction = buildSolanaInstruction(order); /// we are directly calling the ForwarderSolana - forwarderSolana.callSolana(solanaInstruction); + forwarderSolana.callSolana(abi.encode(solanaInstruction)); emit Transferred(_getCurrentRequestCount()); } @@ -102,7 +102,7 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { function mintSuperTokenSolana(SolanaInstruction memory solanaInstruction) external async { // we are directly calling the ForwarderSolana - forwarderSolana.callSolana(solanaInstruction); + forwarderSolana.callSolana(abi.encode(solanaInstruction)); emit Transferred(_getCurrentRequestCount()); } @@ -111,11 +111,17 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { // ISuperToken(order.srcEvmToken).burn(order.userEvm, order.srcAmount); // we are directly calling the ForwarderSolana - forwarderSolana.callSolana(solanaInstruction); + + forwarderSolana.callSolana(abi.encode(solanaInstruction)); emit Transferred(_getCurrentRequestCount()); } + function readAccount(bytes memory solanaReadPayload) external async { + _setOverrides(Read.ON); + forwarderSolana.callSolana(solanaReadPayload); + } + /* function buildSolanaInstruction( TransferOrderEvmToSolana memory order From d718485960db1888b9ffd2cee61565d230e813fd Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Mon, 30 Jun 2025 15:14:00 +0200 Subject: [PATCH 06/18] Fix decoding and always wrapping types into uint256 --- contracts/evmx/watcher/BorshDecdoer.sol | 14 ++++----- test/BorshDecoderTest.t.sol | 42 ++++++++++++------------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/contracts/evmx/watcher/BorshDecdoer.sol b/contracts/evmx/watcher/BorshDecdoer.sol index bc8f8eb3..98625fb8 100644 --- a/contracts/evmx/watcher/BorshDecdoer.sol +++ b/contracts/evmx/watcher/BorshDecdoer.sol @@ -5,9 +5,9 @@ import "../../utils/common/Structs.sol"; library BorshDecoder { // decodes the borsh schema into abi.encode(value) list of params - // all numeric u8/u16/u32/u64 borsh types are decoded and abi encoded as uint256 + // all numeric u8/u16/u32/u64 borsh types are decoded and abi encoded // borsh "String" is decoded and abi encoded as string - // all array/Vec numeric borsh types are decoded and abi encoded as uint256[] + // all array/Vec numeric borsh types are decoded and abi encoded // array/Vec of String borsh type is decoded and encoded string[] // finally all abi encoded params are returned as bytes[] preserving the same order as in GenericSchema function decodeGenericSchema( @@ -22,19 +22,19 @@ library BorshDecoder { if (keccak256(bytes(typeName)) == keccak256(bytes("u8"))) { uint8 value = data.decodeU8(); - decodedParams[i] = abi.encode(uint256(value)); + decodedParams[i] = abi.encode(value); } else if (keccak256(bytes(typeName)) == keccak256(bytes("u16"))) { uint16 value = data.decodeU16(); - decodedParams[i] = abi.encode(uint256(value)); + decodedParams[i] = abi.encode(value); } else if (keccak256(bytes(typeName)) == keccak256(bytes("u32"))) { uint32 value = data.decodeU32(); - decodedParams[i] = abi.encode(uint256(value)); + decodedParams[i] = abi.encode(value); } else if (keccak256(bytes(typeName)) == keccak256(bytes("u64"))) { uint64 value = data.decodeU64(); - decodedParams[i] = abi.encode(uint256(value)); + decodedParams[i] = abi.encode(value); } else if (keccak256(bytes(typeName)) == keccak256(bytes("u128"))) { uint128 value = data.decodeU128(); - decodedParams[i] = abi.encode(uint256(value)); + decodedParams[i] = abi.encode(value); } else if (keccak256(bytes(typeName)) == keccak256(bytes("String"))) { string memory value = data.decodeString(); decodedParams[i] = abi.encode(value); diff --git a/test/BorshDecoderTest.t.sol b/test/BorshDecoderTest.t.sol index 774238d2..6ef3a9e2 100644 --- a/test/BorshDecoderTest.t.sol +++ b/test/BorshDecoderTest.t.sol @@ -426,19 +426,19 @@ contract BorshDecoderTest is Test { assertEq(decodedParams.length, 5); // Check decoded values - uint256 decodedU8 = abi.decode(decodedParams[0], (uint256)); + uint8 decodedU8 = abi.decode(decodedParams[0], (uint8)); assertEq(decodedU8, 42); - uint256 decodedU16 = abi.decode(decodedParams[1], (uint256)); + uint16 decodedU16 = abi.decode(decodedParams[1], (uint16)); assertEq(decodedU16, 1234); - uint256 decodedU32 = abi.decode(decodedParams[2], (uint256)); + uint32 decodedU32 = abi.decode(decodedParams[2], (uint32)); assertEq(decodedU32, 0x12345678); - uint256 decodedU64 = abi.decode(decodedParams[3], (uint256)); + uint64 decodedU64 = abi.decode(decodedParams[3], (uint64)); assertEq(decodedU64, 0x123456789abcdef0); - uint256 decodedU128 = abi.decode(decodedParams[4], (uint256)); + uint128 decodedU128 = abi.decode(decodedParams[4], (uint128)); assertEq(decodedU128, 0x123456789abcdef0fedcba9876543210); } @@ -461,7 +461,7 @@ contract BorshDecoderTest is Test { assertEq(decodedParams.length, 1); // Check decoded u8 vector - uint256[] memory decodedU8Vec = abi.decode(decodedParams[0], (uint256[])); + uint8[] memory decodedU8Vec = abi.decode(decodedParams[0], (uint8[])); assertEq(decodedU8Vec.length, 3); assertEq(decodedU8Vec[0], 1); assertEq(decodedU8Vec[1], 2); @@ -509,7 +509,7 @@ contract BorshDecoderTest is Test { // console.logBytes(decodedParams[0]); // Check decoded u8 array - uint256[] memory decodedU8Array = abi.decode(decodedParams[0], (uint256[])); + uint8[] memory decodedU8Array = abi.decode(decodedParams[0], (uint8[])); assertEq(decodedU8Array.length, 3); assertEq(decodedU8Array[0], 1); assertEq(decodedU8Array[1], 2); @@ -519,7 +519,7 @@ contract BorshDecoderTest is Test { // console.logBytes(decodedParams[1]); // Check decoded u16 array - uint256[] memory decodedU16Array = abi.decode(decodedParams[1], (uint256[])); + uint16[] memory decodedU16Array = abi.decode(decodedParams[1], (uint16[])); assertEq(decodedU16Array.length, 2); assertEq(decodedU16Array[0], 1000); assertEq(decodedU16Array[1], 2000); @@ -568,23 +568,23 @@ contract BorshDecoderTest is Test { assertEq(decodedParams.length, 6); // Check decoded u8 - uint256 decodedU8 = abi.decode(decodedParams[0], (uint256)); - assertEq(decodedU8, uint256(u8Value)); + uint8 decodedU8 = abi.decode(decodedParams[0], (uint8)); + assertEq(decodedU8, u8Value); // Check decoded u32 - uint256 decodedU32 = abi.decode(decodedParams[1], (uint256)); - assertEq(decodedU32, uint256(u32Value)); + uint32 decodedU32 = abi.decode(decodedParams[1], (uint32)); + assertEq(decodedU32, u32Value); // Check decoded u64 (first) - uint256 decodedU64_1 = abi.decode(decodedParams[2], (uint256)); - assertEq(decodedU64_1, uint256(u64Value1)); + uint64 decodedU64_1 = abi.decode(decodedParams[2], (uint64)); + assertEq(decodedU64_1, u64Value1); // Check decoded u64 (second) - uint256 decodedU64_2 = abi.decode(decodedParams[3], (uint256)); - assertEq(decodedU64_2, uint256(u64Value2)); + uint64 decodedU64_2 = abi.decode(decodedParams[3], (uint64)); + assertEq(decodedU64_2, u64Value2); // Check decoded u8 array [u8; 4] - uint256[] memory decodedU8Array = abi.decode(decodedParams[4], (uint256[])); + uint8[] memory decodedU8Array = abi.decode(decodedParams[4], (uint8[])); assertEq(decodedU8Array.length, 4); assertEq(decodedU8Array[0], 10); assertEq(decodedU8Array[1], 20); @@ -592,10 +592,10 @@ contract BorshDecoderTest is Test { assertEq(decodedU8Array[3], 40); // Check decoded u32 array [u32; 10] - uint256[] memory decodedU32Array = abi.decode(decodedParams[5], (uint256[])); + uint32[] memory decodedU32Array = abi.decode(decodedParams[5], (uint32[])); assertEq(decodedU32Array.length, 10); for (uint256 i = 0; i < 10; i++) { - assertEq(decodedU32Array[i], uint256(1000 + i * 100)); + assertEq(decodedU32Array[i], uint32(1000 + i * 100)); } } @@ -637,8 +637,8 @@ contract BorshDecoderTest is Test { assertEq(decodedString, singleString); // Check decoded u32 - uint256 decodedU32 = abi.decode(decodedParams[1], (uint256)); - assertEq(decodedU32, uint256(numberValue)); + uint32 decodedU32 = abi.decode(decodedParams[1], (uint32)); + assertEq(decodedU32, numberValue); // Check decoded string vector string[] memory decodedStringVec = abi.decode(decodedParams[2], (string[])); From ade46e5051926b780e83f35d5ad93a0a454e05ff Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Mon, 30 Jun 2025 16:10:57 +0200 Subject: [PATCH 07/18] Add test for decoding Socket config account from Solana --- test/BorshDecoderTest.t.sol | 68 ++++++++++++++++++++++++++++++++----- 1 file changed, 60 insertions(+), 8 deletions(-) diff --git a/test/BorshDecoderTest.t.sol b/test/BorshDecoderTest.t.sol index 6ef3a9e2..47e82109 100644 --- a/test/BorshDecoderTest.t.sol +++ b/test/BorshDecoderTest.t.sol @@ -82,15 +82,18 @@ contract BorshDecoderTest is Test { assertEq(decoded, originalValue); } - // function testDecodeStringSpecialChars() public pure { - // string memory originalValue = "Hello World! @#$%^&*()"; - // bytes memory encoded = BorshEncoder.encodeString(originalValue); + function testDecodeStringSpecialChars() public pure { + string memory originalValue = "0.1.0"; + bytes memory encoded = BorshEncoder.encodeString(originalValue); + + console.log("encoded 0.1.0"); + console.logBytes(encoded); - // BorshDecoder.Data memory data = BorshDecoder.from(encoded); - // string memory decoded = data.decodeString(); + BorshDecoder.Data memory data = BorshDecoder.from(encoded); + string memory decoded = data.decodeString(); - // assertEq(decoded, originalValue); - // } + assertEq(decoded, originalValue); + } /** Test Vector type decoding **/ @@ -653,10 +656,59 @@ contract BorshDecoderTest is Test { assertEq(decodedStringArray[1], "array2"); } + /** Real-life Solana accounts decoding **/ + + function testDecodeSolanaSocketConfigAccount() public { + GenericSchema memory schema; + schema.valuesTypeNames = new string[](5); + schema.valuesTypeNames[0] = "[u8;8]"; // account discriminator + schema.valuesTypeNames[1] = "[u8;32]"; + schema.valuesTypeNames[2] = "u32"; + schema.valuesTypeNames[3] = "String"; + schema.valuesTypeNames[4] = "u8"; + + bytes8 discriminator = 0x9b0caae01efacc82; + bytes32 owner = 0x0c1a5886fe1093df9fc438c296f9f7275b7718b6bc0e156d8d336c58f083996d; + uint32 chain_slug = 10000002; + string memory version = "0.1.0"; + uint8 bump = 255; + + bytes memory solanaEncodedData = hex"9b0caae01efacc820c1a5886fe1093df9fc438c296f9f7275b7718b6bc0e156d8d336c58f083996d8296980005000000302e312e30ff0000000000"; + + bytes[] memory decodedParams = BorshDecoder.decodeGenericSchema(schema, solanaEncodedData); + + assertEq(decodedParams.length, 5); + + console.log("decoded discriminator"); + // console.logBytes(decodedParams[0]); + uint8[] memory decodedDiscriminator = abi.decode(decodedParams[0], (uint8[])); + bytes memory packedUint8Array = BorshEncoder.packUint8Array(decodedDiscriminator); + assertEq(packedUint8Array, abi.encodePacked(discriminator)); + + console.log("decoded owner"); + uint8[] memory decodedOwner = abi.decode(decodedParams[1], (uint8[])); + packedUint8Array = BorshEncoder.packUint8Array(decodedOwner); + assertEq(packedUint8Array, abi.encodePacked(owner)); + + console.log("decoded chain_slug"); + uint32 decodedChainSlug = abi.decode(decodedParams[2], (uint32)); + assertEq(decodedChainSlug, chain_slug); + + console.log("decoded version"); + string memory decodedVersion = abi.decode(decodedParams[3], (string)); + console.log("decodedVersion"); + console.log(decodedVersion); + assertEq(decodedVersion, version); + + console.log("decoded bump"); + uint8 decodedBump = abi.decode(decodedParams[4], (uint8)); + assertEq(decodedBump, bump); + } + function testDecodeSuperTokenConfigGenericSchema() public pure { GenericSchema memory schema; schema.valuesTypeNames = new string[](5); - schema.valuesTypeNames[0] = "[u8;8]"; + schema.valuesTypeNames[0] = "[u8;8]"; // account discriminator schema.valuesTypeNames[1] = "[u8;32]"; schema.valuesTypeNames[2] = "[u8;32]"; schema.valuesTypeNames[3] = "[u8;32]"; From 38d756c5763e52dfbbe1fd18517d9a69ab83fe06 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Mon, 30 Jun 2025 16:26:50 +0200 Subject: [PATCH 08/18] Simplify SolanaReadRequest - no need for keeping Generic shema inside read request --- contracts/utils/common/Structs.sol | 12 ++-------- .../EvmSolanaOnchainCalls.s.sol | 23 +++---------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index 821b4502..8aa9796a 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -244,19 +244,11 @@ enum SolanaReadSchemaType { struct SolanaReadRequest { bytes32 accountToRead; SolanaReadSchemaType schemaType; - SolanaReadSchema schema; -} - -struct SolanaReadSchema { - PredefinedSchema predefinedSchema; - GenericSchema genericSchema; -} - -struct PredefinedSchema { // keccak256("schema-name") - bytes32 nameHash; + 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; diff --git a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol index e48b09aa..1174cf81 100644 --- a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol +++ b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol @@ -14,10 +14,7 @@ import { SolanaInstructionData, SolanaInstructionDataDescription, SolanaReadRequest, - SolanaReadSchema, - SolanaReadSchemaType, - PredefinedSchema, - GenericSchema + SolanaReadSchemaType } from "../../contracts/utils/common/Structs.sol"; @@ -246,14 +243,7 @@ contract EvmSolanaOnchainCalls is Script { SolanaReadRequest memory readRequest = SolanaReadRequest({ schemaType: SolanaReadSchemaType.PREDEFINED, accountToRead: accountToRead, - schema: SolanaReadSchema({ - predefinedSchema: PredefinedSchema({ - nameHash: schemaNameHash - }), - genericSchema: GenericSchema({ - valuesTypeNames: new string[](0) - }) - }) + predefinedSchemaNameHash: schemaNameHash }); return readRequest; } @@ -262,14 +252,7 @@ contract EvmSolanaOnchainCalls is Script { SolanaReadRequest memory readRequest = SolanaReadRequest({ schemaType: SolanaReadSchemaType.GENERIC, accountToRead: accountToRead, - schema: SolanaReadSchema({ - predefinedSchema: PredefinedSchema({ - nameHash: bytes32(0) - }), - genericSchema: GenericSchema({ - valuesTypeNames: valuesTypeNames - }) - }) + predefinedSchemaNameHash: bytes32(0) }); return readRequest; } From 6fd707c3bf32a900152a9348758a02848f2d429b Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Tue, 1 Jul 2025 17:14:19 +0200 Subject: [PATCH 09/18] Fix naming for BorshDecoder; add handling of Solana READ in the AppGateway --- FunctionSignatures.md | 2 +- contracts/evmx/helpers/ForwarderSolana.sol | 6 +- .../{BorshDecdoer.sol => BorshDecoder.sol} | 0 foundry.toml | 6 +- .../DeployEVMSolanaApps.s.sol | 2 +- .../EvmSolanaOnchainCalls.s.sol | 59 ++++++------ test/BorshDecoderTest.t.sol | 13 ++- .../super-token/EvmSolanaAppGateway.sol | 95 +++++++++++++++++-- 8 files changed, 133 insertions(+), 50 deletions(-) rename contracts/evmx/watcher/{BorshDecdoer.sol => BorshDecoder.sol} (100%) diff --git a/FunctionSignatures.md b/FunctionSignatures.md index b6374cbc..f0820224 100644 --- a/FunctionSignatures.md +++ b/FunctionSignatures.md @@ -303,7 +303,7 @@ | -------- | --------- | | `addressResolver__` | `0x6a750469` | | `asyncDeployer__` | `0x2a39e801` | -| `callSolana` | `0x8af147d3` | +| `callSolana` | `0x4ef7957b` | | `chainSlug` | `0xb349ba65` | | `deployForwarder__` | `0xd4e3b034` | | `feesManager__` | `0x70568b58` | diff --git a/contracts/evmx/helpers/ForwarderSolana.sol b/contracts/evmx/helpers/ForwarderSolana.sol index d86b180e..e622f5e1 100644 --- a/contracts/evmx/helpers/ForwarderSolana.sol +++ b/contracts/evmx/helpers/ForwarderSolana.sol @@ -76,7 +76,7 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil /// @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(SolanaInstruction memory solanaInstruction, bytes32 switchboardSolana) external { - function callSolana(bytes memory solanaPayload) external { + function callSolana(bytes memory solanaPayload, bytes32 target) external { if (address(addressResolver__) == address(0)) { revert AddressResolverNotSet(); } @@ -102,7 +102,9 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil queueParams.overrideParams = overrideParams; queueParams.transaction = Transaction({ chainSlug: chainSlug, - target: onChainAddress, + // 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 }); queueParams.switchboardType = sbType; diff --git a/contracts/evmx/watcher/BorshDecdoer.sol b/contracts/evmx/watcher/BorshDecoder.sol similarity index 100% rename from contracts/evmx/watcher/BorshDecdoer.sol rename to contracts/evmx/watcher/BorshDecoder.sol diff --git a/foundry.toml b/foundry.toml index 75b76193..16688c3e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -24,8 +24,8 @@ via_ir = false 0xedf1aCca2162532BD03F514B62E1aCA9A22f3387 = "FeesManager" 0xaD1D9175BdcbF91e080364FE385dC8601BBBEA72 = "FeesManagerImpl" 0x9De353dD1131aB4e502590D3a1832652FA316268 = "FeesPool" -0x95991009d86FE1bB06e5352169d6b94D10db09D1 = "ForwarderSolana" -0xb54BFc40ED7716a25AA77540D92120370f6Ed8a0 = "ForwarderSolanaImpl" +0xb0bC05F65EaEC98A837686D77AcC50240d1468F7 = "ForwarderSolana" +0x4Ba121BED687db40f4f17A8E25a22e878543E423 = "ForwarderSolanaImpl" 0xE7C9211D6939d76243B3E88c55bfB125967ec36d = "PromiseResolver" 0x4cdCB7a3cd0D90E0187701600e63DC32CFA8b143 = "ReadPrecompile" 0xd922fd7D4C073594d344DD365585c552D97Fd6A9 = "RequestHandler" @@ -35,4 +35,4 @@ via_ir = false 0xd56Ca3ffdD70509cfA78E33D699Ab7dA1bE07f53 = "WatcherImpl" 0x038F81B90D925d4100317D0BBEbF5ea32d70b5aC = "WritePrecompile" 0x3FcD9B95d6Ed738562e79C56669F8BEbDB9CE626 = "WritePrecompileImpl" -0x245011D31A1BeA7a0745bF235B08d730aE2B222F = "APP_GATEWAY" +0x5b0a2656b79212f7Fa6FD77F9583290386860EC4 = "APP_GATEWAY" diff --git a/script/super-token-solana/DeployEVMSolanaApps.s.sol b/script/super-token-solana/DeployEVMSolanaApps.s.sol index e2c3948f..36b4896e 100644 --- a/script/super-token-solana/DeployEVMSolanaApps.s.sol +++ b/script/super-token-solana/DeployEVMSolanaApps.s.sol @@ -23,7 +23,7 @@ contract DeployEVMSolanaApps is Script { // fill with correct values after deployment bytes32 solanaProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); - address forwarderSolanaAddress = 0x95991009d86FE1bB06e5352169d6b94D10db09D1; + address forwarderSolanaAddress = 0xb0bC05F65EaEC98A837686D77AcC50240d1468F7; // Setting fee payment on Arbitrum Sepolia uint256 fees = 10 ether; diff --git a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol index 1174cf81..55b4a1b0 100644 --- a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol +++ b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol @@ -14,7 +14,8 @@ import { SolanaInstructionData, SolanaInstructionDataDescription, SolanaReadRequest, - SolanaReadSchemaType + SolanaReadSchemaType, + GenericSchema } from "../../contracts/utils/common/Structs.sol"; @@ -57,7 +58,9 @@ contract EvmSolanaOnchainCalls is Script { uint256 srcAmount = 1000000; // mintOnEvm(srcAmount, userEvmAddress, appGateway); // mintOnSolana(srcAmount, userEvmAddress, appGateway); - transferEvmToSolana(srcAmount, userEvmAddress, appGateway); + // transferEvmToSolana(srcAmount, userEvmAddress, appGateway); + // readSolanaSuperTokenConfigAccount(appGateway); + readSolanaTokenAccount(appGateway); } function transferEvmToSolana( @@ -142,44 +145,36 @@ contract EvmSolanaOnchainCalls is Script { console.log("Read token account from Solana"); // put here token account address to be read - bytes32 accountToRead = 0x0000000000000000000000000000000000000000000000000000000000000000; + // alice super token ata: LVuCmGaoHjAGu54dFppzujS1Ti61CBac57taeQbokUr + bytes32 accountToRead = 0x04feb6778939c89983aac734e237dc22f49d7b4418d378a516df15a255d084cb; bytes32 schemaNameHash = TOKEN_ACCOUNT; SolanaReadRequest memory readRequest = buildSolanaReadRequestPredefined(accountToRead, schemaNameHash); - appGateway.readAccount(abi.encode(readRequest)); + appGateway.readTokenAccount(readRequest); } function readSolanaSuperTokenConfigAccount(EvmSolanaAppGateway appGateway) public { console.log("Read generic account from Solana"); - // put here super-token config account address to be read (PDA taken from Solana) - bytes32 accountToRead = 0x0000000000000000000000000000000000000000000000000000000000000000; - /** Solana super-token config schema: - pub struct Config { - pub owner: [u8;32], - pub chain_slug: u32, - #[max_len(10)] - pub version: String, - pub bump: u8 - } - */ + // superTokenConfigPda : jox6eY2gcjaKneNv96TKpjN7f3Rjcpn9dN9ZLNt3Krs + bytes32 accountToRead = 0x0af77affb0a5db632e9bafb98525232515d440861c9942e447c20eefd8883d34; + // TODO:GW: All types recognizable by BorshEncoder must be placed in the constants to avoid hardcoding and confusion with lower/upper case - string[] memory valuesTypeNames = new string[](4); - valuesTypeNames[0] = "[u8;32]"; - valuesTypeNames[1] = "u32"; - valuesTypeNames[2] = "String"; - valuesTypeNames[3] = "u8"; - - SolanaReadRequest memory readRequest = buildSolanaReadRequestGeneric(accountToRead, valuesTypeNames); - // TODO: - // - what happens next how to I get my data back? - // - write borsh decoder (Solidity) for simple types and arrays - used in AppGateway to decode data - // - write borsh decoder (TS) for simple types and arrays - used in watcher to read that data ??? - // - maybe not, maybe watcher just gets the data (in generic case) and this data is decoded on AppGateway ? - // - some decoding need on watcher for Token or Mint accounts which will be predefined - - appGateway.readAccount(abi.encode(readRequest)); + string[] memory valuesTypeNames = new string[](5); + valuesTypeNames[0] = "[u8;8]"; // account discriminator + valuesTypeNames[1] = "[u8;32]"; // owner + valuesTypeNames[2] = "[u8;32]"; // socket + valuesTypeNames[3] = "[u8;32]"; // mint + valuesTypeNames[4] = "u8"; // bump + + GenericSchema memory genericSchema = GenericSchema({ + valuesTypeNames: valuesTypeNames + }); + + SolanaReadRequest memory readRequest = buildSolanaReadRequestGeneric(accountToRead); + + appGateway.readSuperTokenConfigAccount(readRequest, genericSchema); } /*************** builder functions ***************/ @@ -239,7 +234,7 @@ contract EvmSolanaOnchainCalls is Script { }); } - function buildSolanaReadRequestPredefined(bytes32 accountToRead, bytes32 schemaNameHash) internal view returns (SolanaReadRequest memory) { + function buildSolanaReadRequestPredefined(bytes32 accountToRead, bytes32 schemaNameHash) internal pure returns (SolanaReadRequest memory) { SolanaReadRequest memory readRequest = SolanaReadRequest({ schemaType: SolanaReadSchemaType.PREDEFINED, accountToRead: accountToRead, @@ -248,7 +243,7 @@ contract EvmSolanaOnchainCalls is Script { return readRequest; } - function buildSolanaReadRequestGeneric(bytes32 accountToRead, string[] memory valuesTypeNames) internal view returns (SolanaReadRequest memory) { + function buildSolanaReadRequestGeneric(bytes32 accountToRead) internal pure returns (SolanaReadRequest memory) { SolanaReadRequest memory readRequest = SolanaReadRequest({ schemaType: SolanaReadSchemaType.GENERIC, accountToRead: accountToRead, diff --git a/test/BorshDecoderTest.t.sol b/test/BorshDecoderTest.t.sol index 47e82109..ef5dbd28 100644 --- a/test/BorshDecoderTest.t.sol +++ b/test/BorshDecoderTest.t.sol @@ -3,13 +3,22 @@ pragma solidity ^0.8.21; import "forge-std/Test.sol"; import {BorshEncoder} from "../contracts/evmx/watcher/BorshEncoder.sol"; -import {BorshDecoder} from "../contracts/evmx/watcher/BorshDecdoer.sol"; +import {BorshDecoder} from "../contracts/evmx/watcher/BorshDecoder.sol"; import "../contracts/utils/common/Structs.sol"; +import "../contracts/utils/common/Constants.sol"; import "forge-std/console.sol"; contract BorshDecoderTest is Test { using BorshDecoder for BorshDecoder.Data; + + function testPredefinedSchemaHash() public pure { + console.log("TOKEN_ACCOUNT"); + console.logBytes32(TOKEN_ACCOUNT); + console.log("MINT_ACCOUNT"); + console.logBytes32(MINT_ACCOUNT); + } + /** Test primitive type decoding **/ function testDecodeU8() public pure { @@ -658,7 +667,7 @@ contract BorshDecoderTest is Test { /** Real-life Solana accounts decoding **/ - function testDecodeSolanaSocketConfigAccount() public { + function testDecodeSolanaSocketConfigAccount() public pure { GenericSchema memory schema; schema.valuesTypeNames = new string[](5); schema.valuesTypeNames[0] = "[u8;8]"; // account discriminator diff --git a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol index 6355ef7d..ac1569b6 100644 --- a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol +++ b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol @@ -7,12 +7,10 @@ import "./ISuperToken.sol"; import "./SuperToken.sol"; import {SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../../../contracts/utils/common/Structs.sol"; import {ForwarderSolana} from "../../../../contracts/evmx/helpers/ForwarderSolana.sol"; +import {BorshDecoder} from "../../../../contracts/evmx/watcher/BorshDecoder.sol"; +import {BorshEncoder} from "../../../../contracts/evmx/watcher/BorshEncoder.sol"; contract EvmSolanaAppGateway is AppGatewayBase, Ownable { - bytes32 public superTokenEvm = _createContractId("superTokenEvm"); - // solana program address - bytes32 public solanaProgramId; - ForwarderSolana public forwarderSolana; event Transferred(uint40 requestCount); @@ -24,6 +22,8 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { uint256 initialSupply_; } + /** Write input structs **/ + struct TransferOrderEvmToSolana { address srcEvmToken; bytes32 dstSolanaToken; @@ -33,6 +33,34 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { uint256 deadline; } + /** Read output structs **/ + + struct SolanaTokenBalance { + uint64 amount; + uint64 decimals; + } + + struct SuperTokenConfigAccount { + bytes8 accountDiscriminator; + bytes32 owner; + bytes32 socket; + bytes32 mint; + uint8 bump; + } + + event SuperTokenConfigAccountRead(SuperTokenConfigAccount superTokenConfigAccount); + event TokenAccountRead(bytes32 tokenAccountAddress, uint64 amount, uint64 decimals); + + /** Contract data **/ + + bytes32 public superTokenEvm = _createContractId("superTokenEvm"); + // solana program address + bytes32 public solanaProgramId; + ForwarderSolana public forwarderSolana; + + mapping(bytes32 => SolanaTokenBalance) solanaTokenBalances; + SuperTokenConfigAccount superTokenConfigAccount; + constructor( address owner_, uint256 fees_, @@ -88,7 +116,7 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { // SolanaInstruction memory solanaInstruction = buildSolanaInstruction(order); /// we are directly calling the ForwarderSolana - forwarderSolana.callSolana(abi.encode(solanaInstruction)); + forwarderSolana.callSolana(abi.encode(solanaInstruction), solanaInstruction.data.programId); emit Transferred(_getCurrentRequestCount()); } @@ -102,7 +130,7 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { function mintSuperTokenSolana(SolanaInstruction memory solanaInstruction) external async { // we are directly calling the ForwarderSolana - forwarderSolana.callSolana(abi.encode(solanaInstruction)); + forwarderSolana.callSolana(abi.encode(solanaInstruction), solanaInstruction.data.programId); emit Transferred(_getCurrentRequestCount()); } @@ -112,14 +140,63 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { // we are directly calling the ForwarderSolana - forwarderSolana.callSolana(abi.encode(solanaInstruction)); + forwarderSolana.callSolana(abi.encode(solanaInstruction), solanaInstruction.data.programId); emit Transferred(_getCurrentRequestCount()); } - function readAccount(bytes memory solanaReadPayload) external async { + function readSuperTokenConfigAccount( + SolanaReadRequest memory solanaReadRequest, + GenericSchema memory genericSchema + ) external async { + _setOverrides(Read.ON); + forwarderSolana.callSolana(abi.encode(solanaReadRequest), solanaReadRequest.accountToRead); + then(this.storeAndDecodeSuperTokenConfigAccount.selector, abi.encode(genericSchema)); + } + + function storeAndDecodeSuperTokenConfigAccount(bytes memory data, bytes memory returnData) external async { + GenericSchema memory genericSchema = abi.decode(data, (GenericSchema)); + bytes[] memory parsedData = BorshDecoder.decodeGenericSchema(genericSchema, returnData); + + uint8[] memory decodedDiscriminatorArray = abi.decode(parsedData[0], (uint8[])); + bytes8 decodedDiscriminator = bytes8(BorshEncoder.packUint8Array(decodedDiscriminatorArray)); + uint8[] memory decodedOwnerArray = abi.decode(parsedData[1], (uint8[])); + bytes32 decodedOwner = bytes32(BorshEncoder.packUint8Array(decodedOwnerArray)); + uint8[] memory decodedSocketArray = abi.decode(parsedData[2], (uint8[])); + bytes32 decodedSocket = bytes32(BorshEncoder.packUint8Array(decodedSocketArray)); + uint8[] memory decodedMintArray = abi.decode(parsedData[3], (uint8[])); + bytes32 decodedMint = bytes32(BorshEncoder.packUint8Array(decodedMintArray)); + uint8 decodedBump = abi.decode(parsedData[4], (uint8)); + + SuperTokenConfigAccount memory decodedSuperTokenConfigAccount = SuperTokenConfigAccount({ + accountDiscriminator: decodedDiscriminator, + owner: decodedOwner, + socket: decodedSocket, + mint: decodedMint, + bump: decodedBump + }); + + superTokenConfigAccount = decodedSuperTokenConfigAccount; + + emit SuperTokenConfigAccountRead(decodedSuperTokenConfigAccount); + } + + function readTokenAccount(SolanaReadRequest memory solanaReadRequest) external async { _setOverrides(Read.ON); - forwarderSolana.callSolana(solanaReadPayload); + + forwarderSolana.callSolana(abi.encode(solanaReadRequest), solanaReadRequest.accountToRead); + then(this.storeTokenAccountData.selector, abi.encode(solanaReadRequest.accountToRead)); + } + + function storeTokenAccountData(bytes memory data, bytes memory returnData) external async { + bytes32 tokenAccountAddress = abi.decode(data, (bytes32)); + (uint64 amount, uint64 decimals) = abi.decode(returnData, (uint64, uint64)); + solanaTokenBalances[tokenAccountAddress] = SolanaTokenBalance({ + amount: amount, + decimals: decimals + }); + + emit TokenAccountRead(tokenAccountAddress, amount, decimals); } /* From 41e6f8af907c9f1ecedae9c2e23d559e3d00b384 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Tue, 1 Jul 2025 18:19:51 +0200 Subject: [PATCH 10/18] Cleanup; Move Borsh encoder/decoder to borsh-serde directory --- .../{ => borsh-serde}/BorshDecoder.sol | 183 ++++-------------- .../{ => borsh-serde}/BorshEncoder.sol | 84 ++------ .../evmx/watcher/borsh-serde/BorshUtils.sol | 125 ++++++++++++ .../watcher/precompiles/WritePrecompile.sol | 2 +- .../DeployEVMSolanaApps.s.sol | 3 - .../EvmSolanaOnchainCalls.s.sol | 17 -- test/BorshDecoderTest.t.sol | 4 +- test/BorshEncoderTest.t.sol | 2 +- .../super-token/EvmSolanaAppGateway.sol | 13 +- 9 files changed, 180 insertions(+), 253 deletions(-) rename contracts/evmx/watcher/{ => borsh-serde}/BorshDecoder.sol (62%) rename contracts/evmx/watcher/{ => borsh-serde}/BorshEncoder.sol (82%) create mode 100644 contracts/evmx/watcher/borsh-serde/BorshUtils.sol diff --git a/contracts/evmx/watcher/BorshDecoder.sol b/contracts/evmx/watcher/borsh-serde/BorshDecoder.sol similarity index 62% rename from contracts/evmx/watcher/BorshDecoder.sol rename to contracts/evmx/watcher/borsh-serde/BorshDecoder.sol index 98625fb8..014c80ee 100644 --- a/contracts/evmx/watcher/BorshDecoder.sol +++ b/contracts/evmx/watcher/borsh-serde/BorshDecoder.sol @@ -1,15 +1,16 @@ // 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 "../../../utils/common/Structs.sol"; +import "./BorshUtils.sol"; library BorshDecoder { - // decodes the borsh schema into abi.encode(value) list of params - // all numeric u8/u16/u32/u64 borsh types are decoded and abi encoded - // borsh "String" is decoded and abi encoded as string - // all array/Vec numeric borsh types are decoded and abi encoded - // array/Vec of String borsh type is decoded and encoded string[] - // finally all abi encoded params are returned as bytes[] preserving the same order as in GenericSchema + /// 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 @@ -72,28 +73,28 @@ library BorshDecoder { decodedParams[i] = abi.encode(value); } // Handle Array types with fixed length - else if (startsWith(typeName, "[u8;")) { - uint256 length = extractArrayLength(typeName); + else if (BorshUtils.startsWith(typeName, "[u8;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); uint8[] memory value = decodeUint8Array(data, length); decodedParams[i] = abi.encode(value); - } else if (startsWith(typeName, "[u16;")) { - uint256 length = extractArrayLength(typeName); + } else if (BorshUtils.startsWith(typeName, "[u16;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); uint16[] memory value = decodeUint16Array(data, length); decodedParams[i] = abi.encode(value); - } else if (startsWith(typeName, "[u32;")) { - uint256 length = extractArrayLength(typeName); + } else if (BorshUtils.startsWith(typeName, "[u32;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); uint32[] memory value = decodeUint32Array(data, length); decodedParams[i] = abi.encode(value); - } else if (startsWith(typeName, "[u64;")) { - uint256 length = extractArrayLength(typeName); + } else if (BorshUtils.startsWith(typeName, "[u64;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); uint64[] memory value = decodeUint64Array(data, length); decodedParams[i] = abi.encode(value); - } else if (startsWith(typeName, "[u128;")) { - uint256 length = extractArrayLength(typeName); + } else if (BorshUtils.startsWith(typeName, "[u128;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); uint128[] memory value = decodeUint128Array(data, length); decodedParams[i] = abi.encode(value); - } else if (startsWith(typeName, "[String;")) { - uint256 length = extractArrayLength(typeName); + } else if (BorshUtils.startsWith(typeName, "[String;")) { + uint256 length = BorshUtils.extractArrayLength(typeName); string[] memory value = decodeStringArray(data, length); decodedParams[i] = abi.encode(value); } else { @@ -113,6 +114,8 @@ library BorshDecoder { uint256 end; } + /********* Helper to manage data pointer *********/ + function from(bytes memory data) internal pure returns (Data memory res) { uint256 ptr; assembly { @@ -120,7 +123,7 @@ library BorshDecoder { } unchecked { res.ptr = ptr + 32; - res.end = res.ptr + readMemory(ptr); + res.end = res.ptr + BorshUtils.readMemory(ptr); } } @@ -133,7 +136,7 @@ library BorshDecoder { function read(Data memory data, uint256 length) internal pure returns (bytes32 res) { data.requireSpace(length); - res = bytes32(readMemory(data.ptr)); + res = bytes32(BorshUtils.readMemory(data.ptr)); unchecked { data.ptr += length; } @@ -144,28 +147,30 @@ library BorshDecoder { 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 swapBytes2(uint16(bytes2(data.read(2)))); + return BorshUtils.swapBytes2(uint16(bytes2(data.read(2)))); } function decodeU32(Data memory data) internal pure returns (uint32) { - return swapBytes4(uint32(bytes4(data.read(4)))); + return BorshUtils.swapBytes4(uint32(bytes4(data.read(4)))); } function decodeU64(Data memory data) internal pure returns (uint64) { - return swapBytes8(uint64(bytes8(data.read(8)))); + return BorshUtils.swapBytes8(uint64(bytes8(data.read(8)))); } function decodeU128(Data memory data) internal pure returns (uint128) { - return swapBytes16(uint128(bytes16(data.read(16)))); + return BorshUtils.swapBytes16(uint128(bytes16(data.read(16)))); } function decodeU256(Data memory data) internal pure returns (uint256) { - return swapBytes32(uint256(data.read(32))); + return BorshUtils.swapBytes32(uint256(data.read(32))); } function decodeBytes20(Data memory data) internal pure returns (bytes20) { @@ -193,7 +198,7 @@ library BorshDecoder { function decodeBytes(Data memory data) internal pure returns (bytes memory res) { uint256 length = data.decodeU32(); data.requireSpace(length); - res = memoryToBytes(data.ptr, length); + res = BorshUtils.memoryToBytes(data.ptr, length); unchecked { data.ptr += length; } @@ -332,127 +337,5 @@ library BorshDecoder { } return values; - } - - - /********* Helper byte-swap functions *********/ - // TODO:GW: move to Utils.sol - can we just like that copy Aurora code ?????? - - 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/BorshEncoder.sol b/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol similarity index 82% rename from contracts/evmx/watcher/BorshEncoder.sol rename to contracts/evmx/watcher/borsh-serde/BorshEncoder.sol index fec8fb59..b498059e 100644 --- a/contracts/evmx/watcher/BorshEncoder.sol +++ b/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol @@ -1,7 +1,9 @@ // 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 "../../../utils/common/Structs.sol"; +import "./BorshUtils.sol"; library BorshEncoder { function encodeFunctionArgs( @@ -43,7 +45,7 @@ library BorshEncoder { functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); } // Handle array types with fixed length - else if (startsWith(typeName, "[u8;")) { + else if (BorshUtils.startsWith(typeName, "[u8;")) { uint8[] memory abiDecodedArg = abi.decode(data, (uint8[])); bytes memory borshEncodedArg = encodeUint8Array(abiDecodedArg); functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); @@ -95,27 +97,27 @@ library BorshEncoder { functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); } // Handle array types with fixed length - no length prefix, just the bytes - else if (startsWith(typeName, "[u8;")) { + 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 (startsWith(typeName, "[u16;")) { + } 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 (startsWith(typeName, "[u32;")) { + } 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 (startsWith(typeName, "[u64;")) { + } 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 (startsWith(typeName, "[u128;")) { + } 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 (startsWith(typeName, "[String;")) { + } else if (BorshUtils.startsWith(typeName, "[String;")) { string[] memory abiDecodedArg = abi.decode(data, (string[])); bytes memory borshEncodedArg = encodeStringArray(abiDecodedArg); functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); @@ -135,19 +137,19 @@ library BorshEncoder { } function encodeU16(uint16 v) internal pure returns (bytes2) { - return bytes2(swapBytes2(v)); + return bytes2(BorshUtils.swapBytes2(v)); } function encodeU32(uint32 v) internal pure returns (bytes4) { - return bytes4(swapBytes4(v)); + return bytes4(BorshUtils.swapBytes4(v)); } function encodeU64(uint64 v) internal pure returns (bytes8) { - return bytes8(swapBytes8(v)); + return bytes8(BorshUtils.swapBytes8(v)); } function encodeU128(uint128 v) internal pure returns (bytes16) { - return bytes16(swapBytes16(v)); + return bytes16(BorshUtils.swapBytes16(v)); } /// Encode bytes vector into borsh. Use this method to encode strings as well. @@ -218,64 +220,6 @@ library BorshEncoder { return packStringArray(arr); } - /********* Helper byte-swap functions *********/ - - 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; - } - /********* Packing functions *********/ // NOTE: diff --git a/contracts/evmx/watcher/borsh-serde/BorshUtils.sol b/contracts/evmx/watcher/borsh-serde/BorshUtils.sol new file mode 100644 index 00000000..06a5d9ad --- /dev/null +++ b/contracts/evmx/watcher/borsh-serde/BorshUtils.sol @@ -0,0 +1,125 @@ +// 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/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index 1e236b0c..25a13c2e 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -10,7 +10,7 @@ import {InvalidIndex, MaxMsgValueLimitExceeded, InvalidPayloadSize} from "../../ import "../../../utils/RescueFundsLib.sol"; import "../WatcherBase.sol"; import {toBytes32Format} from "../../../utils/common/Converters.sol"; -import "../BorshEncoder.sol"; +import "../borsh-serde/BorshEncoder.sol"; abstract contract WritePrecompileStorage is IPrecompile { // slots [0-49] reserved for gap diff --git a/script/super-token-solana/DeployEVMSolanaApps.s.sol b/script/super-token-solana/DeployEVMSolanaApps.s.sol index 36b4896e..584af6f0 100644 --- a/script/super-token-solana/DeployEVMSolanaApps.s.sol +++ b/script/super-token-solana/DeployEVMSolanaApps.s.sol @@ -43,9 +43,6 @@ contract DeployEVMSolanaApps is Script { addressResolver ); - // TODO: deploy super token on evm - // TODO: callSolana() on gateway - console.log("Contracts deployed:"); console.log("EvmSolanaAppGateway:", address(gateway)); console.log("solanaProgramId:"); diff --git a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol index 55b4a1b0..491ec4da 100644 --- a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol +++ b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol @@ -38,23 +38,6 @@ contract EvmSolanaOnchainCalls is Script { console.logBytes32(switchboardSolana); console.log("User address: ", userEvmAddress); - // console.log("Deploying SuperToken on Optimism Sepolia..."); - // appGateway.deployEvmContract(11155420); - - // appGateway.transfer( - // abi.encode( - // EvmSolanaAppGateway.TransferOrderEvmToSolana({ - // srcEvmToken: 0x4200000000000000000000000000000000000006, - // dstSolanaToken: 0x66619ffe200970bf084fa4713da27d7dff551179adac93fc552787c7555f3482, - // userEvm: 0x4200000000000000000000000000000000000005, - // destUserTokenAddress: 0x44419ffe200970bf084fa4713da27d7dff551179adac93fc552787c7555f3482, - // srcAmount: 1000000000000000000, - // deadline: 1715702400 - // }) - // ), - // switchboardSolana - // ); - uint256 srcAmount = 1000000; // mintOnEvm(srcAmount, userEvmAddress, appGateway); // mintOnSolana(srcAmount, userEvmAddress, appGateway); diff --git a/test/BorshDecoderTest.t.sol b/test/BorshDecoderTest.t.sol index ef5dbd28..3b7e01ad 100644 --- a/test/BorshDecoderTest.t.sol +++ b/test/BorshDecoderTest.t.sol @@ -2,8 +2,8 @@ pragma solidity ^0.8.21; import "forge-std/Test.sol"; -import {BorshEncoder} from "../contracts/evmx/watcher/BorshEncoder.sol"; -import {BorshDecoder} from "../contracts/evmx/watcher/BorshDecoder.sol"; +import {BorshEncoder} from "../contracts/evmx/watcher/borsh-serde/BorshEncoder.sol"; +import {BorshDecoder} from "../contracts/evmx/watcher/borsh-serde/BorshDecoder.sol"; import "../contracts/utils/common/Structs.sol"; import "../contracts/utils/common/Constants.sol"; import "forge-std/console.sol"; diff --git a/test/BorshEncoderTest.t.sol b/test/BorshEncoderTest.t.sol index 0c9bb860..57b19be8 100644 --- a/test/BorshEncoderTest.t.sol +++ b/test/BorshEncoderTest.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import "forge-std/Test.sol"; import "forge-std/console.sol"; -import {BorshEncoder} from "../contracts/evmx/watcher/BorshEncoder.sol"; +import {BorshEncoder} from "../contracts/evmx/watcher/borsh-serde/BorshEncoder.sol"; contract BorshEncoderTest is Test { /** Encode primitive types **/ diff --git a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol index ac1569b6..f5a5cd84 100644 --- a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol +++ b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol @@ -7,8 +7,8 @@ import "./ISuperToken.sol"; import "./SuperToken.sol"; import {SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../../../contracts/utils/common/Structs.sol"; import {ForwarderSolana} from "../../../../contracts/evmx/helpers/ForwarderSolana.sol"; -import {BorshDecoder} from "../../../../contracts/evmx/watcher/BorshDecoder.sol"; -import {BorshEncoder} from "../../../../contracts/evmx/watcher/BorshEncoder.sol"; +import {BorshDecoder} from "../../../../contracts/evmx/watcher/borsh-serde/BorshDecoder.sol"; +import {BorshEncoder} from "../../../../contracts/evmx/watcher/borsh-serde/BorshEncoder.sol"; contract EvmSolanaAppGateway is AppGatewayBase, Ownable { @@ -113,9 +113,7 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { TransferOrderEvmToSolana memory order = abi.decode(order_, (TransferOrderEvmToSolana)); ISuperToken(order.srcEvmToken).burn(order.userEvm, order.srcAmount); - // SolanaInstruction memory solanaInstruction = buildSolanaInstruction(order); - - /// we are directly calling the ForwarderSolana + // we are directly calling the ForwarderSolana forwarderSolana.callSolana(abi.encode(solanaInstruction), solanaInstruction.data.programId); emit Transferred(_getCurrentRequestCount()); @@ -135,11 +133,8 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { emit Transferred(_getCurrentRequestCount()); } + // this is only for debugging purposes to mint tokens on Solana function transferForDebug(SolanaInstruction memory solanaInstruction) external async { - // ISuperToken(order.srcEvmToken).burn(order.userEvm, order.srcAmount); - - // we are directly calling the ForwarderSolana - forwarderSolana.callSolana(abi.encode(solanaInstruction), solanaInstruction.data.programId); emit Transferred(_getCurrentRequestCount()); From 7003cd13e59f748b3f5f5fc3a2381cf2fa612f44 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Wed, 2 Jul 2025 14:19:10 +0200 Subject: [PATCH 11/18] Comment to remove the onChainAddress_ from initialize() in ForwarderSolana --- contracts/evmx/helpers/ForwarderSolana.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/evmx/helpers/ForwarderSolana.sol b/contracts/evmx/helpers/ForwarderSolana.sol index e622f5e1..989e9903 100644 --- a/contracts/evmx/helpers/ForwarderSolana.sol +++ b/contracts/evmx/helpers/ForwarderSolana.sol @@ -30,7 +30,7 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil /// @param addressResolver_ address resolver contract function initialize( uint32 chainSlug_, - bytes32 onChainAddress_, + 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) { From 549a766c07f49770628b9ac2c1a57e81e4cde60a Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Thu, 3 Jul 2025 13:45:01 +0200 Subject: [PATCH 12/18] fix: lost letter --- contracts/evmx/watcher/precompiles/WritePrecompile.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index be56971f..6bc8192b 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -155,7 +155,7 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc { ( address appGateway, - Transaction memory transaction,– + Transaction memory transaction, , uint256 gasLimit, uint256 value, From 86b8a2913c37e58fa7c4a41e29d9cf2648d12246 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Wed, 16 Jul 2025 14:11:51 +0200 Subject: [PATCH 13/18] feat: invoke trigger and read return value final changes --- contracts/evmx/base/AppGatewayBase.sol | 3 + foundry.toml | 50 +++--- .../DeployEVMSolanaApps.s.sol | 2 +- .../EvmSolanaOnchainCalls.s.sol | 144 ++++++++++++++---- setupInfraContracts.sh | 2 +- test/DigestTest.t.sol | 24 +++ test/ReturnValueSolanaTest.t.sol | 40 +++++ .../super-token/EvmSolanaAppGateway.sol | 88 +++++------ 8 files changed, 243 insertions(+), 110 deletions(-) create mode 100644 test/ReturnValueSolanaTest.t.sol diff --git a/contracts/evmx/base/AppGatewayBase.sol b/contracts/evmx/base/AppGatewayBase.sol index f7e5f5ca..542c9a41 100644 --- a/contracts/evmx/base/AppGatewayBase.sol +++ b/contracts/evmx/base/AppGatewayBase.sol @@ -159,6 +159,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { /// @param contractId_ The contract ID /// @param chainSlug_ The chain slug /// @return onChainAddress The on-chain address + // TODO:GW: this does not work for Solana, as setAddress() is not called - cos forwarder and solana contract are not deployed with AG function getOnChainAddress( bytes32 contractId_, uint32 chainSlug_ @@ -216,6 +217,8 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { /// @param contractId_ The bytes32 identifier of the contract to be validated /// @param isValid Boolean flag indicating whether the contract is authorized (true) or not (false) /// @dev This function retrieves the onchain address using the contractId_ and chainSlug, then calls the watcher precompile to update the plug's validity status + // TODO:GW: this does not work for Solana, as setAddress() is not called - cos forwarder and solana contract are not deployed with AG + // there is no contractId for solana function _setValidPlug(bool isValid, uint32 chainSlug_, bytes32 contractId_) internal { bytes32 onchainAddress = getOnChainAddress(contractId_, chainSlug_); watcher__().setIsValidPlug(isValid, chainSlug_, onchainAddress); diff --git a/foundry.toml b/foundry.toml index 16688c3e..9b8a496e 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,29 +10,29 @@ evm_version = 'paris' via_ir = false [labels] -0x10E7a47D76931D6383532C5924ae91De2F540f25 = "AddressResolver" -0x0442aDE746E464387EAEC5f44411aB5E866EC8d2 = "AddressResolverImpl" -0x78b5A6BfC55fC818bd532907c566A48863705C94 = "AsyncDeployer" -0xe3F72951f14d09AA7c44a5b0275472C8572D0Bbe = "AsyncDeployerImpl" -0x39C854C26A339CD567e41902068C829eb9BD93f6 = "AuctionManager" -0x6D820620F087d4B687bb41424B7078CA649a9b26 = "AuctionManagerImpl" -0xF0FA3c9a0d71F3e2BAADF323f6b4F0303C60d69E = "Configurations" -0x3390533482A5f94Ebc4e97CFE4Dc678569857629 = "ConfigurationsImpl" -0xa8cdDce9B5F6C4B1FDefa9e1317C0b3d27ee54b3 = "DeployForwarder" -0x116bcEace2F8aa251bf09cbADF5d186bA5376353 = "DeployForwarderImpl" -0x0d2772e0E5A0F9544e9093E2F57a33B5990c5E49 = "ERC1967Factory" -0xedf1aCca2162532BD03F514B62E1aCA9A22f3387 = "FeesManager" -0xaD1D9175BdcbF91e080364FE385dC8601BBBEA72 = "FeesManagerImpl" +0xE486D1Da5E250a268b5DA7eBc6AF3503bf8288DC = "AddressResolver" +0xf0CaA8D7b0d7be914D1801Ab2d95B1A75fCd4c01 = "AddressResolverImpl" +0x66d59d0bA711241785F1110C163448071c4Dd4Cc = "AsyncDeployer" +0xFe468C6506c212734A998848Da419E8041f47A64 = "AsyncDeployerImpl" +0xD2E1bF41B54ada70590873BE2409d5AD20341E82 = "AuctionManager" +0xfDDC7488C060Bf56107ca3A47918aEf06B59071d = "AuctionManagerImpl" +0x63cbD05da9bE1e37D3fC3eeB27da601FBbA199d1 = "Configurations" +0x36d42DFE5b2aeF21F7750947cEDc8c950f89fC6A = "ConfigurationsImpl" +0x1746948725fc2E2003511c34BE3509FA112A392E = "DeployForwarder" +0x227d6082Ea2D7Ee52b33F0d83B9411297ABf8ecE = "DeployForwarderImpl" +0xc9B4874dDA3AbB27DF56466DD650dd9B30b48986 = "ERC1967Factory" +0x686Be563fa9797aa7F66dCF73321cbd2F7a98874 = "FeesManager" +0x17F2aC81c4f1bee70D4c8ee9a3cb28E4e096De4f = "FeesManagerImpl" 0x9De353dD1131aB4e502590D3a1832652FA316268 = "FeesPool" -0xb0bC05F65EaEC98A837686D77AcC50240d1468F7 = "ForwarderSolana" -0x4Ba121BED687db40f4f17A8E25a22e878543E423 = "ForwarderSolanaImpl" -0xE7C9211D6939d76243B3E88c55bfB125967ec36d = "PromiseResolver" -0x4cdCB7a3cd0D90E0187701600e63DC32CFA8b143 = "ReadPrecompile" -0xd922fd7D4C073594d344DD365585c552D97Fd6A9 = "RequestHandler" -0x738c91b43D6e9c34E4ccDBf661fd926C225Ca222 = "RequestHandlerImpl" -0xAc95d5d8dEf53218792e720E769c3307CB0D5556 = "SchedulePrecompile" -0x98ff886e4EAB2087Bd10589b7E84B57cC2083932 = "Watcher" -0xd56Ca3ffdD70509cfA78E33D699Ab7dA1bE07f53 = "WatcherImpl" -0x038F81B90D925d4100317D0BBEbF5ea32d70b5aC = "WritePrecompile" -0x3FcD9B95d6Ed738562e79C56669F8BEbDB9CE626 = "WritePrecompileImpl" -0x5b0a2656b79212f7Fa6FD77F9583290386860EC4 = "APP_GATEWAY" +0xC570206ACBa112fC8d438235BF8cE31b3548aa96 = "ForwarderSolana" +0x3Fe4DDEd31943B90cB2AdcA39cE1f33Fa4Ab45d8 = "ForwarderSolanaImpl" +0xa0EEEC3D28acF5cF00181130aACf8A63d5F53F94 = "PromiseResolver" +0x78dFD826E6Aef751144987E1E25326De48879869 = "ReadPrecompile" +0xd183b5bf6d77E2D253a1230C2eAE34C18810Fbc3 = "RequestHandler" +0xD05D508704a8984a471E4FCdD57865D36c4A88B7 = "RequestHandlerImpl" +0xD832BFfE0c5e43Ef36dE8a2c50367d3f12DE09B7 = "SchedulePrecompile" +0x2d0fAB18450A7A2885a0130090BfA0B184F0b801 = "Watcher" +0xEEe9A8308b5b2A5814b35D10aeBCDeA760B9117f = "WatcherImpl" +0x1D179869F6079818a1DC852fEbf0890cc6DC558c = "WritePrecompile" +0x9461e4b96d5ceAa749E6Ee535979D1f3A83d5968 = "WritePrecompileImpl" +0xD3A6671C53c57133095b800a10024060a5B6Bf02 = "APP_GATEWAY" diff --git a/script/super-token-solana/DeployEVMSolanaApps.s.sol b/script/super-token-solana/DeployEVMSolanaApps.s.sol index 584af6f0..cb080b09 100644 --- a/script/super-token-solana/DeployEVMSolanaApps.s.sol +++ b/script/super-token-solana/DeployEVMSolanaApps.s.sol @@ -23,7 +23,7 @@ contract DeployEVMSolanaApps is Script { // fill with correct values after deployment bytes32 solanaProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); - address forwarderSolanaAddress = 0xb0bC05F65EaEC98A837686D77AcC50240d1468F7; + address forwarderSolanaAddress = 0xC570206ACBa112fC8d438235BF8cE31b3548aa96; // Setting fee payment on Arbitrum Sepolia uint256 fees = 10 ether; diff --git a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol index 491ec4da..4487f437 100644 --- a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol +++ b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol @@ -6,7 +6,8 @@ import {console} from "forge-std/console.sol"; import { ETH_ADDRESS, TOKEN_ACCOUNT, - MINT_ACCOUNT + MINT_ACCOUNT, + CHAIN_SLUG_SOLANA_DEVNET } from "../../contracts/utils/common/Constants.sol"; import {EvmSolanaAppGateway} from "../../test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol"; import { @@ -32,18 +33,27 @@ contract EvmSolanaOnchainCalls is Script { EvmSolanaAppGateway appGateway = EvmSolanaAppGateway(vm.envAddress("APP_GATEWAY")); bytes32 switchboardSolana = vm.envBytes32("SWITCHBOARD_SOLANA"); address userEvmAddress = vm.envAddress("EVM_TEST_ACCOUNT"); + // bytes32 solanaTargetProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); console.log("EvmSolanaAppGateway:", address(appGateway)); console.log("Switchboard solana:"); console.logBytes32(switchboardSolana); console.log("User address: ", userEvmAddress); + // TODO:GW: fix this so that we can directly use the plug address (changes needed in socket/super-token) + // plug signer pda : aprk6EUeRofYzyABHXLKG8hqJ3GHGV7Uhzbu612UMBD + bytes32 solana_plug_signer = 0x08aa478d7031ac31e2a406e25f1e4dbd00bce5cbd428b7bf5e5b01abfd4b7da8; + // appGateway.setIsValidPlug(CHAIN_SLUG_SOLANA_DEVNET, solanaTargetProgramId); + appGateway.setIsValidPlugForSolana(true, CHAIN_SLUG_SOLANA_DEVNET, solana_plug_signer); + // allow super-token to trigger AppGateway + // appGateway.setIsValidPlug(CHAIN_SLUG_SOLANA_DEVNET, solana_plug_signer); + uint256 srcAmount = 1000000; // mintOnEvm(srcAmount, userEvmAddress, appGateway); - // mintOnSolana(srcAmount, userEvmAddress, appGateway); + mintOnSolana(srcAmount, userEvmAddress, appGateway); // transferEvmToSolana(srcAmount, userEvmAddress, appGateway); // readSolanaSuperTokenConfigAccount(appGateway); - readSolanaTokenAccount(appGateway); + // readSolanaTokenAccount(appGateway); } function transferEvmToSolana( @@ -65,7 +75,7 @@ contract EvmSolanaOnchainCalls is Script { deadline: 1715702400 }); - SolanaInstruction memory solanaInstruction = buildSolanaInstruction(order); + SolanaInstruction memory solanaInstruction = buildMintTokenSolanaInstruction(order); bytes memory orderEncoded = abi.encode(order); @@ -108,7 +118,14 @@ contract EvmSolanaOnchainCalls is Script { ) public { console.log("Mint on Solana"); - SolanaInstruction memory solanaInstruction = buildSolanaInstruction( + string[] memory returnDataValuesTypeNames = new string[](1); + returnDataValuesTypeNames[0] = "Vec"; + + GenericSchema memory returnDataSchema = GenericSchema({ + valuesTypeNames: returnDataValuesTypeNames + }); + + SolanaInstruction memory solanaInstruction = buildMintTokenSolanaInstruction( EvmSolanaAppGateway.TransferOrderEvmToSolana({ srcEvmToken: 0xD4a20b34D0dE11e3382Aaa7E0839844f154B6191, // mint on local-testnet: BdUzPsaAicEWinR7b14YLtvavwM8zYn8BaHKqGQ8by2q @@ -121,7 +138,7 @@ contract EvmSolanaOnchainCalls is Script { }) ); - appGateway.mintSuperTokenSolana(solanaInstruction); + appGateway.mintSuperTokenSolana(solanaInstruction, returnDataSchema); } function readSolanaTokenAccount(EvmSolanaAppGateway appGateway) public { @@ -140,8 +157,8 @@ contract EvmSolanaOnchainCalls is Script { function readSolanaSuperTokenConfigAccount(EvmSolanaAppGateway appGateway) public { console.log("Read generic account from Solana"); - // superTokenConfigPda : jox6eY2gcjaKneNv96TKpjN7f3Rjcpn9dN9ZLNt3Krs - bytes32 accountToRead = 0x0af77affb0a5db632e9bafb98525232515d440861c9942e447c20eefd8883d34; + // superTokenConfigPda : GfNcT3X72r8Cmy2itGCNPUrigsoPvTLH4vy3qaYkSqHx + bytes32 accountToRead = 0xe8b3cf7c50f0b707dba43ef8042a34802d4f1768c72798bafa24d741c89f9ccf; // TODO:GW: All types recognizable by BorshEncoder must be placed in the constants to avoid hardcoding and confusion with lower/upper case string[] memory valuesTypeNames = new string[](5); @@ -160,45 +177,60 @@ contract EvmSolanaOnchainCalls is Script { appGateway.readSuperTokenConfigAccount(readRequest, genericSchema); } + function invokeTrigger( + EvmSolanaAppGateway appGateway + ) public { + console.log("Invoke on Solana"); + + SolanaInstruction memory solanaInstruction = buildTriggerTestSolanaInstruction(); + + appGateway.triggerTestSuperTokenSolana(solanaInstruction); + } + /*************** builder functions ***************/ - function buildSolanaInstruction( + function buildMintTokenSolanaInstruction( EvmSolanaAppGateway.TransferOrderEvmToSolana memory order ) internal view returns (SolanaInstruction memory) { bytes32 solanaTargetProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); // May be subject to change - bytes32[] memory accounts = new bytes32[](5); - // accounts 0 - superTokenConfigPda : jox6eY2gcjaKneNv96TKpjN7f3Rjcpn9dN9ZLNt3Krs - accounts[0] = 0x0af77affb0a5db632e9bafb98525232515d440861c9942e447c20eefd8883d34; - // accounts 1 - mint account - accounts[1] = order.dstSolanaToken; - // accounts 2 - destination user ata - accounts[2] = order.destUserTokenAddress; - // accounts 3 - system programId: 11111111111111111111111111111111 - accounts[3] = 0x0000000000000000000000000000000000000000000000000000000000000000; - // accounts 4 - token programId: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA - accounts[4] = 0x06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9; - - bytes[] memory functionArguments = new bytes[](1); - // TODO:GW: in watcher and transmitter we might need to convert this value if on Solana mint has different decimals, for now we assume that both are the same - functionArguments[0] = abi.encode(order.srcAmount); - - bytes1[] memory accountFlags = new bytes1[](5); + bytes32[] memory accounts = new bytes32[](6); + // -- start here we are missing tmp_data account + // accounts 0 - tmpData pda : EgDiv7JoLPY6CJgfkFKjxsDB6cxC7k9sdS9Baehp1c6L + accounts[0] = 0xcb33f7992e094d6c65e0e95cf54e70c3470840e69d739dfcfcf2e1805fc913d1; + // accounts 1 - superTokenConfigPda : GfNcT3X72r8Cmy2itGCNPUrigsoPvTLH4vy3qaYkSqHx + accounts[1] = 0xe8b3cf7c50f0b707dba43ef8042a34802d4f1768c72798bafa24d741c89f9ccf; + // accounts 2 - mint account + accounts[2] = order.dstSolanaToken; + // accounts 3 - destination user ata + accounts[3] = order.destUserTokenAddress; + // accounts 4 - system programId: 11111111111111111111111111111111 + accounts[4] = 0x0000000000000000000000000000000000000000000000000000000000000000; + // accounts 5 - token programId: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA + accounts[5] = 0x06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9; + + bytes1[] memory accountFlags = new bytes1[](6); + // tmpData pda is writable + accountFlags[0] = bytes1(0x01); // true // superTokenConfigPda is not writable - accountFlags[0] = bytes1(0x00); // false + accountFlags[1] = bytes1(0x00); // false // mint is writable - accountFlags[1] = bytes1(0x01); // true - // destination user ata is writable accountFlags[2] = bytes1(0x01); // true + // destination user ata is writable + accountFlags[3] = bytes1(0x01); // true // system programId is not writable - accountFlags[3] = bytes1(0x00); // false - // token programId is not writable accountFlags[4] = bytes1(0x00); // false + // token programId is not writable + accountFlags[5] = bytes1(0x00); // false // mint instruction discriminator bytes8 instructionDiscriminator = 0x3339e12fb69289a6; + bytes[] memory functionArguments = new bytes[](1); + // TODO:GW: in watcher and transmitter we might need to convert this value if on Solana mint has different decimals, for now we assume that both are the same + functionArguments[0] = abi.encode(order.srcAmount); + string[] memory functionArgumentTypeNames = new string[](1); functionArgumentTypeNames[0] = "u64"; @@ -217,6 +249,52 @@ contract EvmSolanaOnchainCalls is Script { }); } + function buildTriggerTestSolanaInstruction() internal view returns (SolanaInstruction memory) { + bytes32 solanaTargetProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); + + // May be subject to change + bytes32[] memory accounts = new bytes32[](5); + // accounts 0 - plug signer pda : aprk6EUeRofYzyABHXLKG8hqJ3GHGV7Uhzbu612UMBD + accounts[0] = 0x08aa478d7031ac31e2a406e25f1e4dbd00bce5cbd428b7bf5e5b01abfd4b7da8; + // accounts 1 - trigger counter pda : 4EYucSHVdCt5BK9N95peyVCi7weQTBoW5Tf3a2nQbRbf + accounts[1] = 0x300bb0522a1ad67d5c5d8fe3102e0ac9b05f04436d4831609d158067b5bd4cda; + // accounts 2 - socket programId: 9vFEQ5e3xf4eo17WttfqmXmnqN3gUicrhFGppmmNwyqV + accounts[2] = 0x84815e8ca2f6dad7e12902c39a51bc72e13c48139b4fb10025d94e7abea2969c; + // accounts 3 - system programId: 11111111111111111111111111111111 + accounts[3] = 0x0000000000000000000000000000000000000000000000000000000000000000; + + bytes1[] memory accountFlags = new bytes1[](4); + // plug signer is not writable + accountFlags[0] = bytes1(0x00); // false + // trigger counter is writable + accountFlags[1] = bytes1(0x01); // true + // socket programId is not writable + accountFlags[2] = bytes1(0x00); // false + // system programId is not writable + accountFlags[3] = bytes1(0x00); // false + + // trigger_test instruction discriminator + bytes8 instructionDiscriminator = 0x4058d836fd208fb1; + + bytes[] memory functionArguments = new bytes[](0); + + string[] memory functionArgumentTypeNames = new string[](0); + + return + SolanaInstruction({ + data: SolanaInstructionData({ + programId: solanaTargetProgramId, + instructionDiscriminator: instructionDiscriminator, + accounts: accounts, + functionArguments: functionArguments + }), + description: SolanaInstructionDataDescription({ + accountFlags: accountFlags, + functionArgumentTypeNames: functionArgumentTypeNames + }) + }); + } + function buildSolanaReadRequestPredefined(bytes32 accountToRead, bytes32 schemaNameHash) internal pure returns (SolanaReadRequest memory) { SolanaReadRequest memory readRequest = SolanaReadRequest({ schemaType: SolanaReadSchemaType.PREDEFINED, @@ -245,8 +323,8 @@ contract EvmSolanaOnchainCalls is Script { // May be subject to change bytes32[] memory accounts = new bytes32[](5); - // accounts 0 - superTokenConfigPda : jox6eY2gcjaKneNv96TKpjN7f3Rjcpn9dN9ZLNt3Krs - accounts[0] = 0x0af77affb0a5db632e9bafb98525232515d440861c9942e447c20eefd8883d34; + // accounts 0 - superTokenConfigPda : GfNcT3X72r8Cmy2itGCNPUrigsoPvTLH4vy3qaYkSqHx + accounts[0] = 0xe8b3cf7c50f0b707dba43ef8042a34802d4f1768c72798bafa24d741c89f9ccf; // accounts 1 - mint account accounts[1] = order.dstSolanaToken; // accounts 2 - destination user ata diff --git a/setupInfraContracts.sh b/setupInfraContracts.sh index 57b29e44..c08fed42 100644 --- a/setupInfraContracts.sh +++ b/setupInfraContracts.sh @@ -16,4 +16,4 @@ time npx hardhat run hardhat-scripts/misc-scripts/eventTopics.ts --no-compile time npx hardhat run hardhat-scripts/misc-scripts/functionSigs.ts --no-compile time npx ts-node hardhat-scripts/misc-scripts/createLabels.ts time npx hardhat run hardhat-scripts/verify/verify.ts --no-compile -# yarn lint \ No newline at end of file +yarn lint \ No newline at end of file diff --git a/test/DigestTest.t.sol b/test/DigestTest.t.sol index b7ea0ffc..02ed6120 100644 --- a/test/DigestTest.t.sol +++ b/test/DigestTest.t.sol @@ -20,6 +20,30 @@ contract DigestTest is Test { console.logBytes32(superTokenEvm); } + function testFunctionSelectorEncoding() public pure { + bytes4 selector = bytes4(keccak256("increase(uint256,uint32,uint8[],uint32[],string)")); + uint8[] memory array_one = new uint8[](3); + array_one[0] = 1; + array_one[1] = 2; + array_one[2] = 3; + uint32[] memory array_two = new uint32[](3); + array_two[0] = 666; + array_two[1] = 777; + array_two[2] = 888; + + bytes memory callData = abi.encodeWithSelector( + selector, + 123456, + 666, + array_one, + array_two, + "hello world" + ); + + console.log("Full calldata"); + console.logBytes(callData); + } + function testDigest() public pure { bytes32 expectedDigest = 0xc26b01718c6f97b51ad73743bb5b1ac2abb53966d15a2948f65db43b30cce1a1; diff --git a/test/ReturnValueSolanaTest.t.sol b/test/ReturnValueSolanaTest.t.sol new file mode 100644 index 00000000..5c2ed803 --- /dev/null +++ b/test/ReturnValueSolanaTest.t.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "forge-std/Test.sol"; +import {GenericSchema} from "../contracts/utils/common/Structs.sol"; +import "../contracts/utils/common/Constants.sol"; +import {BorshDecoder} from "../contracts/evmx/watcher/borsh-serde/BorshDecoder.sol"; +import {BorshEncoder} from "../contracts/evmx/watcher/borsh-serde/BorshEncoder.sol"; +import "forge-std/console.sol"; + +contract ReturnValueSolanaTest is Test { + function testDecoding() public { + string[] memory returnDataValuesTypeNames = new string[](2); + returnDataValuesTypeNames[0] = "[u8; 32]"; + returnDataValuesTypeNames[1] = "Vec"; + + GenericSchema memory returnDataSchema = GenericSchema({ + valuesTypeNames: returnDataValuesTypeNames + }); + + bytes memory returnData = hex"0c1a5886fe1093df9fc438c296f9f7275b7718b6bc0e156d8d336c58f083996d0400000001020304"; + + // GenericSchema memory genericSchema = abi.decode(data, (GenericSchema)); + bytes[] memory parsedData = BorshDecoder.decodeGenericSchema(returnDataSchema, returnData); + + uint8[] memory transmitterSolanaArray = abi.decode(parsedData[0], (uint8[])); + bytes memory transmitterSolana = BorshEncoder.packUint8Array(transmitterSolanaArray); + + uint8[] memory returnValueArray = abi.decode(parsedData[1], (uint8[])); + bytes memory returnValue = BorshEncoder.packUint8Array(returnValueArray); + + console.logBytes(transmitterSolana); + console.logBytes(returnValue); + + // bytes32 hexadecimal representation of solana transmitter address + assertEq(transmitterSolana, hex"0c1a5886fe1093df9fc438c296f9f7275b7718b6bc0e156d8d336c58f083996d"); + // 4 bytes with values: 1, 2, 3, 4 + assertEq(returnValue, hex"01020304"); + } +} \ No newline at end of file diff --git a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol index f5a5cd84..b295ea78 100644 --- a/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol +++ b/test/apps/app-gateways/super-token/EvmSolanaAppGateway.sol @@ -48,8 +48,12 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { uint8 bump; } + /** Events **/ + event SuperTokenConfigAccountRead(SuperTokenConfigAccount superTokenConfigAccount); event TokenAccountRead(bytes32 tokenAccountAddress, uint64 amount, uint64 decimals); + event TriggerIncrease(uint256 amountU64, uint32 amountU32, uint8[] vecU8, uint32[] vecU32, string myString, uint256 triggerCounter); + event MintReturnData(bytes data); /** Contract data **/ @@ -61,6 +65,8 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { mapping(bytes32 => SolanaTokenBalance) solanaTokenBalances; SuperTokenConfigAccount superTokenConfigAccount; + uint256 triggerCounter; + constructor( address owner_, uint256 fees_, @@ -106,6 +112,12 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { return address(forwarderSolana.addressResolver__()); } + // we have to do it like that as onchain contract is not deployed with AG + // more info in : AppGatewayBase.sol -> _setValidPlug() and getOnChainAddress() + function setIsValidPlugForSolana(bool isValid, uint32 chainSlug_, bytes32 plugAddress) public { + watcher__().setIsValidPlug(isValid, chainSlug_, plugAddress); + } + function transfer( bytes memory order_, SolanaInstruction memory solanaInstruction @@ -126,7 +138,18 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { emit Transferred(_getCurrentRequestCount()); } - function mintSuperTokenSolana(SolanaInstruction memory solanaInstruction) external async { + function mintSuperTokenSolana( + SolanaInstruction memory solanaInstruction, + GenericSchema memory returnDataSchema + ) external async { + // we are directly calling the ForwarderSolana + forwarderSolana.callSolana(abi.encode(solanaInstruction), solanaInstruction.data.programId); + then(this.storeAndDecodeMintReturnData.selector, abi.encode(returnDataSchema)); + + emit Transferred(_getCurrentRequestCount()); + } + + function triggerTestSuperTokenSolana(SolanaInstruction memory solanaInstruction) external async { // we are directly calling the ForwarderSolana forwarderSolana.callSolana(abi.encode(solanaInstruction), solanaInstruction.data.programId); @@ -176,6 +199,16 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { emit SuperTokenConfigAccountRead(decodedSuperTokenConfigAccount); } + function storeAndDecodeMintReturnData(bytes memory data, bytes memory returnData) external async { + GenericSchema memory genericSchema = abi.decode(data, (GenericSchema)); + bytes[] memory parsedData = BorshDecoder.decodeGenericSchema(genericSchema, returnData); + + uint8[] memory decodedReturnDataArray = abi.decode(parsedData[0], (uint8[])); + bytes memory decodedReturnData = BorshEncoder.packUint8Array(decodedReturnDataArray); + + emit MintReturnData(decodedReturnData); + } + function readTokenAccount(SolanaReadRequest memory solanaReadRequest) external async { _setOverrides(Read.ON); @@ -194,53 +227,8 @@ contract EvmSolanaAppGateway is AppGatewayBase, Ownable { emit TokenAccountRead(tokenAccountAddress, amount, decimals); } - /* - function buildSolanaInstruction( - TransferOrderEvmToSolana memory order - ) internal view returns (SolanaInstruction memory) { - // May be subject to change - bytes32[] memory accounts = new bytes32[](5); - // accounts 0 - destination user wallet - accounts[0] = order.destUserTokenAddress; - // accounts 1 - mint account - accounts[1] = order.dstSolanaToken; - // accounts 2 - user ata account for mint // TODO:GW: this is random value - accounts[2] = 0x66619ffe200970bf084fa4713da27d7dff551179adac93fc552787c7555f3482; - // accounts 4 - mint authority account (target program PDA) // TODO:GW: this is random value - accounts[4] = 0xfff2e2d5bdb632266e17b0cdce8b7e3f3a7f1d87c096719f234903b39f84d743; - // accounts 5,6 - system_program, token_program (those are static and will be added by the transmitter while making a call) - - bytes[] memory functionArguments = new bytes[](1); - // TODO:GW: in watcher and transmitter we might need to convert this value if on Solana mint has different decimals, for now we assume that both are the same - functionArguments[0] = abi.encode(order.srcAmount); - - bytes1[] memory accountFlags = new bytes1[](4); - accountFlags[0] = bytes1(0x00); - // mint must be is writable - accountFlags[1] = bytes1(0x01); - // dst token ata must be is writable - accountFlags[2] = bytes1(0x01); - accountFlags[3] = bytes1(0x00); - - // TODO:GW: update when TargetDummy is ready - bytes8 instructionDiscriminator = bytes8(uint64(123)); - - string[] memory functionArgumentTypeNames = new string[](1); - functionArgumentTypeNames[0] = "u64"; - - return - SolanaInstruction({ - data: SolanaInstructionData({ - programId: solanaProgramId, - instructionDiscriminator: instructionDiscriminator, - accounts: accounts, - functionArguments: functionArguments - }), - description: SolanaInstructionDataDescription({ - accountFlags: accountFlags, - functionArgumentTypeNames: functionArgumentTypeNames - }) - }); - } - */ + function increase(uint256 amountU64, uint32 amountU32, uint8[] memory vecU8, uint32[] memory vecU32, string memory myString) public { + triggerCounter++; + emit TriggerIncrease(amountU64, amountU32, vecU8, vecU32, myString, triggerCounter); + } } From a09b06a649332d27bb696838d76d44576587677f Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Wed, 16 Jul 2025 14:47:54 +0200 Subject: [PATCH 14/18] remove comments --- contracts/evmx/watcher/precompiles/WritePrecompile.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index 25a13c2e..500a0839 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -162,12 +162,12 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc { ( address appGateway, - Transaction memory transaction, // _writeFinality + Transaction memory transaction, , uint256 gasLimit, uint256 value, - ) = // bytes32 switchboard + ) = abi.decode( payloadParams.precompileData, (address, Transaction, WriteFinality, uint256, uint256, bytes32) From 701e866fe4f6217c4fe2e3bc76374a284c5b0397 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Thu, 17 Jul 2025 16:44:06 +0200 Subject: [PATCH 15/18] CR fixes --- Errors.md | 12 -------- EventTopics.md | 2 -- contracts/evmx/helpers/ForwarderSolana.sol | 19 ------------- contracts/evmx/watcher/Configurations.sol | 11 ++------ contracts/evmx/watcher/RequestHandler.sol | 28 ------------------- .../watcher/precompiles/WritePrecompile.sol | 13 --------- 6 files changed, 2 insertions(+), 83 deletions(-) diff --git a/Errors.md b/Errors.md index 9ff586ce..9f8047f5 100644 --- a/Errors.md +++ b/Errors.md @@ -39,18 +39,6 @@ | `InvalidDepositAmount()` | `0xfe9ba5cd` | | `TokenNotWhitelisted(address)` | `0xea3bff2e` | -## evmx/watcher/Configurations.sol - -| Error | Signature | -| ----------------------------------------- | ------------ | -| `InvalidSwitchboardTest(bytes32,bytes32)` | `0x702f36a1` | - -## evmx/watcher/Configurations.sol - -| Error | Signature | -|-------|-----------| -| `InvalidSwitchboardTest(bytes32,bytes32)` | `0x702f36a1` | -| `InvalidGatewayTest(bytes32,bytes32,bytes32)` | `0xc6ad0fcf` | ## evmx/watcher/RequestHandler.sol diff --git a/EventTopics.md b/EventTopics.md index 589e474b..c860edeb 100644 --- a/EventTopics.md +++ b/EventTopics.md @@ -212,7 +212,6 @@ | `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | | `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | | `OwnershipTransferred` | `(oldOwner: address, newOwner: address)` | `0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0` | -| `PayloadIdData` | `(requestCount: uint40, batchCount: uint40, payloadCount: uint40, chainSlug: uint32, switchboard: bytes32, packed: bytes)` | `0x40cd955f96207785cb90a63920257959a7ca0c5f89377eaefbe53ccaa8e4b380` | | `RequestCancelled` | `(requestCount: uint40)` | `0xff191657769be72fc08def44c645014c60d18cb24b9ca05c9a33406a28253245` | | `RequestCompletedWithErrors` | `(requestCount: uint40)` | `0xd8d9915dc14b5a29b66cb263e1ea1e99e60418fc21d97f0fbf09cae1281291e2` | | `RequestSettled` | `(requestCount: uint40, winner: address)` | `0x1234f98acbe1548b214f4528461a5377f1e2349569c04caa59325e488e7d2aa4` | @@ -266,7 +265,6 @@ | ----- | --------- | ----- | | `ChainMaxMsgValueLimitsUpdated` | `(chainSlug: uint32, maxMsgValueLimit: uint256)` | `0x439087d094fe7dacbba3f0c67032041952d8bd58a891e15af10ced28fed0eb91` | | `ContractFactoryPlugSet` | `(chainSlug: uint32, contractFactoryPlug: bytes32)` | `0xfad552a6feb82bef23201b8dce04b2460bff41b00f26fef3d791572cfdab49c2` | -| `DigestWithSourceParams` | `(digest: bytes32, digestParams: tuple)` | `0x093e9b93d5aafe2a01beeb5ad1e80356601b533260651c08eda18b79912f315b` | | `ExpiryTimeSet` | `(expiryTime: uint256)` | `0x07e837e13ad9a34715a6bd45f49bbf12de19f06df79cb0be12b3a7d7f2397fa9` | | `FeesSet` | `(writeFees: uint256)` | `0x3346af6da1932164d501f2ec28f8c5d686db5828a36b77f2da4332d89184fe7b` | | `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | diff --git a/contracts/evmx/helpers/ForwarderSolana.sol b/contracts/evmx/helpers/ForwarderSolana.sol index 989e9903..7bb9f2e0 100644 --- a/contracts/evmx/helpers/ForwarderSolana.sol +++ b/contracts/evmx/helpers/ForwarderSolana.sol @@ -42,25 +42,6 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil _setAddressResolver(addressResolver_); } - /// @notice Stores the callback address and data to be executed once the promise is resolved. - /// @dev This function should not be called before the fallback function. - /// @dev It resets the latest async promise address - /// @param selector_ The function selector for callback - /// @param data_ The data to be passed to callback - /// @return promise_ The address of the new promise - // TODO:GW: uncommenting this is making the deployment fail silently - // function then(bytes4 selector_, bytes memory data_) external returns (address promise_) { - // if (latestAsyncPromise == address(0)) revert NoAsyncPromiseFound(); - // if (latestPromiseCaller != msg.sender) revert PromiseCallerMismatch(); - // if (latestRequestCount != watcherPrecompile__().nextRequestCount()) - // revert RequestCountMismatch(); - - // address latestAsyncPromise_ = latestAsyncPromise; - // latestAsyncPromise = address(0); - - // promise_ = IPromise(latestAsyncPromise_).then(selector_, data_); - // } - /// @notice Returns the on-chain address associated with this forwarder. /// @return The on-chain address. function getOnChainAddress() external view returns (bytes32) { diff --git a/contracts/evmx/watcher/Configurations.sol b/contracts/evmx/watcher/Configurations.sol index a6ff7d14..02726002 100644 --- a/contracts/evmx/watcher/Configurations.sol +++ b/contracts/evmx/watcher/Configurations.sol @@ -14,9 +14,6 @@ abstract contract ConfigurationsStorage is IConfigurations { // slots [0-49] reserved for gap uint256[50] _gap_before; - error InvalidSwitchboardTest(bytes32 sb, bytes32 sbExpected); - error InvalidGatewayTest(bytes32 appGatewayId, bytes32 appGatewayIdExpected, bytes32 switchboard); - // slot 50 /// @notice Maps network and plug to their configuration /// @dev chainSlug => plug => PlugConfig @@ -166,12 +163,8 @@ contract Configurations is ConfigurationsStorage, Initializable, Ownable, Watche bytes32 switchboardType_ ) external view { (bytes32 appGatewayId, bytes32 switchboard) = getPlugConfigs(chainSlug_, target_); - // if (appGatewayId != toBytes32Format(appGateway_)) revert InvalidGateway(); - if (appGatewayId != toBytes32Format(appGateway_)) revert InvalidGatewayTest(appGatewayId, toBytes32Format(appGateway_), switchboard); - // emit VerifyConnectionsSB(switchboard, switchboards[chainSlug_][switchboardType_]); - // if (switchboard != switchboards[chainSlug_][switchboardType_]) revert InvalidSwitchboard(); - if (switchboard != switchboards[chainSlug_][switchboardType_]) - revert InvalidSwitchboardTest(switchboard, switchboards[chainSlug_][switchboardType_]); + if (appGatewayId != toBytes32Format(appGateway_)) revert InvalidGateway(); + if (switchboard != switchboards[chainSlug_][switchboardType_]) revert InvalidSwitchboard(); } /** diff --git a/contracts/evmx/watcher/RequestHandler.sol b/contracts/evmx/watcher/RequestHandler.sol index d9bd933b..ad1a6b71 100644 --- a/contracts/evmx/watcher/RequestHandler.sol +++ b/contracts/evmx/watcher/RequestHandler.sol @@ -75,16 +75,6 @@ contract RequestHandler is RequestHandlerStorage, Initializable, Ownable, Addres event RequestCompletedWithErrors(uint40 requestCount); event RequestCancelled(uint40 requestCount); - // TODO: remove after testing Solana - event PayloadIdData( - uint40 requestCount, - uint40 batchCount, - uint40 payloadCount, - uint32 chainSlug, - bytes32 switchboard, - bytes packed - ); - modifier isRequestCancelled(uint40 requestCount_) { if (_requests[requestCount_].requestTrackingParams.isRequestCancelled) revert RequestAlreadyCancelled(); @@ -264,24 +254,6 @@ contract RequestHandler is RequestHandlerStorage, Initializable, Ownable, Addres ); _batchPayloadIds[nextBatchCount].push(payloadId); - // TODO: remove after testing Solana - uint32 chainSlugParam = queuePayloadParam.transaction.chainSlug; - bytes memory packed = abi.encodePacked( - requestCount_, - nextBatchCount, - payloadCount, - chainSlugParam, - switchboard - ); - emit PayloadIdData( - requestCount_, - nextBatchCount, - payloadCount, - chainSlugParam, - switchboard, - packed - ); - // create prev digest hash PayloadParams memory p; p.requestCount = requestCount_; diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index 4d80f255..28b2c047 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -63,9 +63,6 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc PayloadParams payloadParams ); - // TODO: remove after testing Solana - event DigestWithSourceParams(bytes32 digest, DigestParams digestParams); - /// @notice Emitted when a proof is uploaded /// @param payloadId The unique identifier for the request /// @param proof The proof from the watcher @@ -211,8 +208,6 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc bytes32 digest = getDigest(digestParams_); digestHashes[payloadParams.payloadId] = digest; - emit DigestWithSourceParams(digest, digestParams_); - emit WriteProofRequested( transmitter_, digest, @@ -311,14 +306,6 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc ); bytes memory functionArgsPacked = BorshEncoder.encodeFunctionArgs(instruction); - // for (uint256 i = 0; i < instruction.data.functionArguments.length; i++) { - // uint256 abiDecodedArg = abi.decode(instruction.data.functionArguments[i], (uint256)); - // // silent assumption that all arguments are uint64 to simplify the encoding - // uint64 arg = uint64(abiDecodedArg); - // bytes8 borshEncodedArg = BorshEncoder.encodeU64(arg); - // functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); - // } - bytes memory payloadPacked = abi.encodePacked( instruction.data.programId, instruction.data.accounts, From baaf1fae719842d48bf40354ba2833ba4e3654fd Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Wed, 27 Aug 2025 11:11:17 +0200 Subject: [PATCH 16/18] minor changes --- Errors.md | 190 ++-- EventTopics.md | 2 + FunctionSignatures.md | 933 +++++++++--------- contracts/protocol/SocketConfig.sol | 6 + foundry.toml | 50 +- hardhat-scripts/deploy/3.configureChains.ts | 2 +- .../DeployEVMSolanaApps.s.sol | 2 +- 7 files changed, 597 insertions(+), 588 deletions(-) diff --git a/Errors.md b/Errors.md index 9f8047f5..b5c59429 100644 --- a/Errors.md +++ b/Errors.md @@ -1,20 +1,21 @@ # Custom Error Codes + ## evmx/fees/FeesPool.sol -| Error | Signature | -| ------------------ | ------------ | +| Error | Signature | +|-------|-----------| | `TransferFailed()` | `0x90b8ec18` | ## evmx/helpers/AsyncPromise.sol -| Error | Signature | -| -------------------------- | ------------ | +| Error | Signature | +|-------|-----------| | `PromiseAlreadyResolved()` | `0x56b63537` | -| `OnlyInvoker()` | `0x74ed21f5` | -| `PromiseAlreadySetUp()` | `0x927c53d5` | -| `PromiseRevertFailed()` | `0x0175b9de` | -| `NotLatestPromise()` | `0x39ca95d3` | +| `OnlyInvoker()` | `0x74ed21f5` | +| `PromiseAlreadySetUp()` | `0x927c53d5` | +| `PromiseRevertFailed()` | `0x0175b9de` | +| `NotLatestPromise()` | `0x39ca95d3` | ## evmx/helpers/ForwarderSolana.sol @@ -25,132 +26,131 @@ ## evmx/plugs/ContractFactoryPlug.sol -| Error | Signature | -| -------------------------------- | ------------ | -| `DeploymentFailed()` | `0x30116425` | +| Error | Signature | +|-------|-----------| +| `DeploymentFailed()` | `0x30116425` | | `ExecutionFailed(bytes32,bytes)` | `0xd255d8a3` | -| `information(bool,,bytes)` | `0x3a82a1f3` | +| `information(bool,,bytes)` | `0x3a82a1f3` | ## evmx/plugs/FeesPlug.sol -| Error | Signature | -| --------------------------------------------------- | ------------ | +| Error | Signature | +|-------|-----------| | `InsufficientTokenBalance(address,uint256,uint256)` | `0xebd6ced9` | -| `InvalidDepositAmount()` | `0xfe9ba5cd` | -| `TokenNotWhitelisted(address)` | `0xea3bff2e` | - +| `InvalidDepositAmount()` | `0xfe9ba5cd` | +| `TokenNotWhitelisted(address)` | `0xea3bff2e` | ## evmx/watcher/RequestHandler.sol -| Error | Signature | -| ----------------------- | ------------ | +| Error | Signature | +|-------|-----------| | `InsufficientMaxFees()` | `0x0e5bc492` | ## protocol/Socket.sol -| Error | Signature | -| ----------------------------------------- | ------------ | +| Error | Signature | +|-------|-----------| | `PayloadAlreadyExecuted(ExecutionStatus)` | `0xf4c54edd` | -| `VerificationFailed()` | `0x439cc0cd` | -| `LowGasLimit()` | `0xd38edae0` | -| `InsufficientMsgValue()` | `0x78f38f76` | +| `VerificationFailed()` | `0x439cc0cd` | +| `LowGasLimit()` | `0xd38edae0` | +| `InsufficientMsgValue()` | `0x78f38f76` | ## protocol/SocketConfig.sol -| Error | Signature | -| ------------------------------- | ------------ | -| `SwitchboardExists()` | `0x2dff8555` | +| Error | Signature | +|-------|-----------| +| `SwitchboardExists()` | `0x2dff8555` | | `SwitchboardExistsOrDisabled()` | `0x1c7d2487` | ## protocol/SocketFeeManager.sol -| Error | Signature | -| -------------------- | ------------ | +| Error | Signature | +|-------|-----------| | `InsufficientFees()` | `0x8d53e553` | -| `FeeTooLow()` | `0x732f9413` | +| `FeeTooLow()` | `0x732f9413` | ## protocol/SocketUtils.sol -| Error | Signature | -| -------------------- | ------------ | -| `OnlyOffChain()` | `0x9cbfe066` | +| Error | Signature | +|-------|-----------| +| `OnlyOffChain()` | `0x9cbfe066` | | `SimulationFailed()` | `0x2fbab3ac` | ## protocol/switchboard/FastSwitchboard.sol -| Error | Signature | -| ------------------- | ------------ | +| Error | Signature | +|-------|-----------| | `AlreadyAttested()` | `0x35d90805` | | `WatcherNotFound()` | `0xa278e4ad` | ## utils/AccessControl.sol -| Error | Signature | -| ------------------- | ------------ | +| Error | Signature | +|-------|-----------| | `NoPermit(bytes32)` | `0x962f6333` | ## utils/common/Converters.sol -| Error | Signature | -| -------------------------- | ------------ | +| Error | Signature | +|-------|-----------| | `NotAnEvmAddress(bytes32)` | `0x33b960d0` | ## utils/common/Errors.sol -| Error | Signature | -| --------------------------------------------- | ------------ | -| `ZeroAddress()` | `0xd92e233d` | -| `InvalidTransmitter()` | `0x58a70a0a` | -| `InvalidTokenAddress()` | `0x1eb00b06` | -| `InvalidSwitchboard()` | `0xf63c9e4d` | -| `SocketAlreadyInitialized()` | `0xc9500b00` | -| `NotSocket()` | `0xc59f8f7c` | -| `PlugNotFound()` | `0x5f1ac76a` | -| `ResolvingScheduleTooEarly()` | `0x207e8731` | -| `CallFailed()` | `0x3204506f` | -| `InvalidAppGateway()` | `0x82ded261` | -| `AppGatewayAlreadyCalled()` | `0xb224683f` | -| `InvalidCallerTriggered()` | `0x3292d247` | -| `InvalidPromise()` | `0x45f2d176` | -| `InvalidWatcherSignature()` | `0x5029f14f` | -| `NonceUsed()` | `0x1f6d5aef` | -| `AsyncModifierNotSet()` | `0xcae106f9` | -| `WatcherNotSet()` | `0x42d473a7` | -| `InvalidTarget()` | `0x82d5d76a` | -| `InvalidIndex()` | `0x63df8171` | -| `InvalidChainSlug()` | `0xbff6b106` | -| `InvalidPayloadSize()` | `0xfbdf7954` | -| `InvalidOnChainAddress()` | `0xb758c606` | -| `InvalidScheduleDelay()` | `0x9a993219` | -| `AuctionClosed()` | `0x36b6b46d` | -| `AuctionNotOpen()` | `0xf0460077` | -| `BidExceedsMaxFees()` | `0x4c923f3c` | -| `LowerBidAlreadyExists()` | `0xaaa1f709` | -| `RequestCountMismatch()` | `0x98bbcbff` | -| `InvalidAmount()` | `0x2c5211c6` | -| `InsufficientCreditsAvailable()` | `0xe61dc0aa` | -| `InsufficientBalance()` | `0xf4d678b8` | -| `InvalidCaller()` | `0x48f5c3ed` | -| `InvalidGateway()` | `0xfc9dfe85` | -| `RequestAlreadyCancelled()` | `0xc70f47d8` | -| `DeadlineNotPassedForOnChainRevert()` | `0x7006aa10` | -| `InvalidBid()` | `0xc6388ef7` | -| `MaxReAuctionCountReached()` | `0xf2b4388c` | -| `MaxMsgValueLimitExceeded()` | `0x97b4e8ce` | -| `OnlyWatcherAllowed()` | `0xdf7d227c` | -| `InvalidPrecompileData()` | `0x320062c0` | -| `InvalidCallType()` | `0x39d2eb55` | -| `NotRequestHandler()` | `0x8f8cba5b` | -| `NotInvoker()` | `0x8a6353d1` | -| `NotPromiseResolver()` | `0x86d876b2` | -| `RequestPayloadCountLimitExceeded()` | `0xcbef144b` | -| `InsufficientFees()` | `0x8d53e553` | -| `RequestAlreadySettled()` | `0x66fad465` | -| `NoWriteRequest()` | `0x9dcd3065` | -| `AlreadyAssigned()` | `0x9688dc51` | -| `OnlyAppGateway()` | `0xfec944ea` | +| Error | Signature | +|-------|-----------| +| `ZeroAddress()` | `0xd92e233d` | +| `InvalidTransmitter()` | `0x58a70a0a` | +| `InvalidTokenAddress()` | `0x1eb00b06` | +| `InvalidSwitchboard()` | `0xf63c9e4d` | +| `SocketAlreadyInitialized()` | `0xc9500b00` | +| `NotSocket()` | `0xc59f8f7c` | +| `PlugNotFound()` | `0x5f1ac76a` | +| `ResolvingScheduleTooEarly()` | `0x207e8731` | +| `CallFailed()` | `0x3204506f` | +| `InvalidAppGateway()` | `0x82ded261` | +| `AppGatewayAlreadyCalled()` | `0xb224683f` | +| `InvalidCallerTriggered()` | `0x3292d247` | +| `InvalidPromise()` | `0x45f2d176` | +| `InvalidWatcherSignature()` | `0x5029f14f` | +| `NonceUsed()` | `0x1f6d5aef` | +| `AsyncModifierNotSet()` | `0xcae106f9` | +| `WatcherNotSet()` | `0x42d473a7` | +| `InvalidTarget()` | `0x82d5d76a` | +| `InvalidIndex()` | `0x63df8171` | +| `InvalidChainSlug()` | `0xbff6b106` | +| `InvalidPayloadSize()` | `0xfbdf7954` | +| `InvalidOnChainAddress()` | `0xb758c606` | +| `InvalidScheduleDelay()` | `0x9a993219` | +| `AuctionClosed()` | `0x36b6b46d` | +| `AuctionNotOpen()` | `0xf0460077` | +| `BidExceedsMaxFees()` | `0x4c923f3c` | +| `LowerBidAlreadyExists()` | `0xaaa1f709` | +| `RequestCountMismatch()` | `0x98bbcbff` | +| `InvalidAmount()` | `0x2c5211c6` | +| `InsufficientCreditsAvailable()` | `0xe61dc0aa` | +| `InsufficientBalance()` | `0xf4d678b8` | +| `InvalidCaller()` | `0x48f5c3ed` | +| `InvalidGateway()` | `0xfc9dfe85` | +| `RequestAlreadyCancelled()` | `0xc70f47d8` | +| `DeadlineNotPassedForOnChainRevert()` | `0x7006aa10` | +| `InvalidBid()` | `0xc6388ef7` | +| `MaxReAuctionCountReached()` | `0xf2b4388c` | +| `MaxMsgValueLimitExceeded()` | `0x97b4e8ce` | +| `OnlyWatcherAllowed()` | `0xdf7d227c` | +| `InvalidPrecompileData()` | `0x320062c0` | +| `InvalidCallType()` | `0x39d2eb55` | +| `NotRequestHandler()` | `0x8f8cba5b` | +| `NotInvoker()` | `0x8a6353d1` | +| `NotPromiseResolver()` | `0x86d876b2` | +| `RequestPayloadCountLimitExceeded()` | `0xcbef144b` | +| `InsufficientFees()` | `0x8d53e553` | +| `RequestAlreadySettled()` | `0x66fad465` | +| `NoWriteRequest()` | `0x9dcd3065` | +| `AlreadyAssigned()` | `0x9688dc51` | +| `OnlyAppGateway()` | `0xfec944ea` | | `NewMaxFeesLowerThanCurrent(uint256,uint256)` | `0x1345dda1` | -| `InvalidContract()` | `0x6eefed20` | -| `InvalidData()` | `0x5cb045db` | -| `InvalidSignature()` | `0x8baa579f` | -| `DeadlinePassed()` | `0x70f65caa` | +| `InvalidContract()` | `0x6eefed20` | +| `InvalidData()` | `0x5cb045db` | +| `InvalidSignature()` | `0x8baa579f` | +| `DeadlinePassed()` | `0x70f65caa` | diff --git a/EventTopics.md b/EventTopics.md index c860edeb..97781ebe 100644 --- a/EventTopics.md +++ b/EventTopics.md @@ -221,6 +221,8 @@ | Event | Arguments | Topic | | ----- | --------- | ----- | +| `AppGatewayCallFailed` | `(triggerId: bytes32)` | `0xcaf8475fdade8465ea31672463949e6cf1797fdcdd11eeddbbaf857e1e5907b7` | +| `CalledAppGateway` | `(triggerId: bytes32)` | `0xf659ffb3875368f54fb4ab8f5412ac4518af79701a48076f7a58d4448e4bdd0b` | | `Initialized` | `(version: uint64)` | `0xc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d2` | | `OwnershipHandoverCanceled` | `(pendingOwner: address)` | `0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92` | | `OwnershipHandoverRequested` | `(pendingOwner: address)` | `0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d` | diff --git a/FunctionSignatures.md b/FunctionSignatures.md index 6663b725..7e29232c 100644 --- a/FunctionSignatures.md +++ b/FunctionSignatures.md @@ -2,300 +2,300 @@ ## AuctionManager -| Function | Signature | -| ---------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `asyncDeployer__` | `0x2a39e801` | -| `auctionEndDelaySeconds` | `0x9087dfdb` | -| `auctionManager` | `0xb0192f9a` | -| `auctionStatus` | `0xd7d5fbf6` | -| `bid` | `0xfcdf49c2` | -| `bidTimeout` | `0x94090d0b` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `consumeFrom` | `0x40dd78be` | -| `creationCodeWithArgs` | `0xc126dcc4` | -| `deployForwarder__` | `0xd4e3b034` | -| `endAuction` | `0x1212e653` | -| `evmxSlug` | `0x8bae77c2` | -| `expireBid` | `0x1dd5022c` | -| `feesManager__` | `0x70568b58` | -| `forwarderAddresses` | `0x5390fdcb` | -| `getOnChainAddress` | `0xb6abffd7` | -| `getOverrideParams` | `0x54f0a866` | -| `grantRole` | `0x2f2ff15d` | -| `handleRevert` | `0x44792f25` | -| `hasRole` | `0x91d14854` | -| `initialize` | `0x86891c9b` | -| `initializeOnChain` | `0x86f01739` | -| `isAsyncModifierSet` | `0xb69e0c4a` | -| `isValidPromise` | `0xb690b962` | -| `maxFees` | `0xe83e34b1` | -| `maxReAuctionCount` | `0xc367b376` | -| `onCompleteData` | `0xb52fa926` | -| `onRequestComplete` | `0x5ed1f959` | -| `overrideParams` | `0xec5490fe` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `addressResolver__` | `0x6a750469` | +| `asyncDeployer__` | `0x2a39e801` | +| `auctionEndDelaySeconds` | `0x9087dfdb` | +| `auctionManager` | `0xb0192f9a` | +| `auctionStatus` | `0xd7d5fbf6` | +| `bid` | `0xfcdf49c2` | +| `bidTimeout` | `0x94090d0b` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `consumeFrom` | `0x40dd78be` | +| `creationCodeWithArgs` | `0xc126dcc4` | +| `deployForwarder__` | `0xd4e3b034` | +| `endAuction` | `0x1212e653` | +| `evmxSlug` | `0x8bae77c2` | +| `expireBid` | `0x1dd5022c` | +| `feesManager__` | `0x70568b58` | +| `forwarderAddresses` | `0x5390fdcb` | +| `getOnChainAddress` | `0xb6abffd7` | +| `getOverrideParams` | `0x54f0a866` | +| `grantRole` | `0x2f2ff15d` | +| `handleRevert` | `0x44792f25` | +| `hasRole` | `0x91d14854` | +| `initialize` | `0x86891c9b` | +| `initializeOnChain` | `0x86f01739` | +| `isAsyncModifierSet` | `0xb69e0c4a` | +| `isValidPromise` | `0xb690b962` | +| `maxFees` | `0xe83e34b1` | +| `maxReAuctionCount` | `0xc367b376` | +| `onCompleteData` | `0xb52fa926` | +| `onRequestComplete` | `0x5ed1f959` | +| `overrideParams` | `0xec5490fe` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `reAuctionCount` | `0x9b4b22d3` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `revokeRole` | `0xd547741f` | -| `sbType` | `0x745de344` | -| `setAddress` | `0x85bf312c` | -| `setAuctionEndDelaySeconds` | `0x88606b1a` | -| `setMaxReAuctionCount` | `0x64c71403` | -| `transferOwnership` | `0xf2fde38b` | -| `watcher__` | `0x300bb063` | -| `winningBids` | `0x9133f232` | +| `reAuctionCount` | `0x9b4b22d3` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `revokeRole` | `0xd547741f` | +| `sbType` | `0x745de344` | +| `setAddress` | `0x85bf312c` | +| `setAuctionEndDelaySeconds` | `0x88606b1a` | +| `setMaxReAuctionCount` | `0x64c71403` | +| `transferOwnership` | `0xf2fde38b` | +| `watcher__` | `0x300bb063` | +| `winningBids` | `0x9133f232` | ## Socket -| Function | Signature | -| ---------------------------- | ------------ | -| `OFF_CHAIN_CALLER` | `0xcb2cb4f6` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `chainSlug` | `0xb349ba65` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `connect` | `0xb3bde1aa` | -| `disableSwitchboard` | `0xc4d9a820` | -| `enableSwitchboard` | `0xf97a498a` | -| `execute` | `0xafa8b480` | -| `getPlugConfig` | `0xf9778ee0` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | -| `isValidSwitchboard` | `0xb2d67675` | -| `maxCopyBytes` | `0x212249d4` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `OFF_CHAIN_CALLER` | `0xcb2cb4f6` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `chainSlug` | `0xb349ba65` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `connect` | `0xb3bde1aa` | +| `disableSwitchboard` | `0xc4d9a820` | +| `enableSwitchboard` | `0xf97a498a` | +| `execute` | `0xafa8b480` | +| `getPlugConfig` | `0xf9778ee0` | +| `grantRole` | `0x2f2ff15d` | +| `hasRole` | `0x91d14854` | +| `isValidSwitchboard` | `0xb2d67675` | +| `maxCopyBytes` | `0x212249d4` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `payloadExecuted` | `0x3eaeac3d` | -| `payloadIdToDigest` | `0x7c8552b2` | -| `registerSwitchboard` | `0x74f5b1fc` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `revokeRole` | `0xd547741f` | -| `setMaxCopyBytes` | `0x4fc7d6e9` | -| `setSocketFeeManager` | `0x25bd97e5` | -| `simulate` | `0x91bf8275` | -| `socketFeeManager` | `0xde5b8838` | -| `transferOwnership` | `0xf2fde38b` | -| `triggerCounter` | `0x8b0021de` | -| `version` | `0x54fd4d50` | +| `payloadExecuted` | `0x3eaeac3d` | +| `payloadIdToDigest` | `0x7c8552b2` | +| `registerSwitchboard` | `0x74f5b1fc` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `revokeRole` | `0xd547741f` | +| `setMaxCopyBytes` | `0x4fc7d6e9` | +| `setSocketFeeManager` | `0x25bd97e5` | +| `simulate` | `0x91bf8275` | +| `socketFeeManager` | `0xde5b8838` | +| `transferOwnership` | `0xf2fde38b` | +| `triggerCounter` | `0x8b0021de` | +| `version` | `0x54fd4d50` | ## SocketBatcher -| Function | Signature | -| ---------------------------- | ------------ | -| `attestAndExecute` | `0x66c7748a` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `attestAndExecute` | `0x66c7748a` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `socket__` | `0xc6a261d2` | -| `transferOwnership` | `0xf2fde38b` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `socket__` | `0xc6a261d2` | +| `transferOwnership` | `0xf2fde38b` | ## SocketFeeManager -| Function | Signature | -| ---------------------------- | ------------ | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `getMinSocketFees` | `0xd383b688` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `getMinSocketFees` | `0xd383b688` | +| `grantRole` | `0x2f2ff15d` | +| `hasRole` | `0x91d14854` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `payAndCheckFees` | `0xd9d29ae3` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `revokeRole` | `0xd547741f` | -| `setSocketFees` | `0x47a406f6` | -| `socketFees` | `0xab1b33a8` | -| `transferOwnership` | `0xf2fde38b` | +| `payAndCheckFees` | `0xd9d29ae3` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `revokeRole` | `0xd547741f` | +| `setSocketFees` | `0x47a406f6` | +| `socketFees` | `0xab1b33a8` | +| `transferOwnership` | `0xf2fde38b` | ## FeesManager -| Function | Signature | -| -------------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `approveAppGateway` | `0xa3b53d8b` | +| Function | Signature | +| -------- | --------- | +| `addressResolver__` | `0x6a750469` | +| `approveAppGateway` | `0xa3b53d8b` | | `approveAppGatewayWithSignature` | `0x94b649ec` | -| `approveAppGateways` | `0x86d23ab2` | -| `asyncDeployer__` | `0x2a39e801` | -| `blockCredits` | `0x9e434307` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `deployForwarder__` | `0xd4e3b034` | -| `deposit` | `0x5671d329` | -| `evmxSlug` | `0x8bae77c2` | -| `feesManager__` | `0x70568b58` | -| `feesPlugs` | `0x23f5ee8a` | -| `feesPool` | `0x6b259690` | -| `getAvailableCredits` | `0xb065a8e5` | -| `handleRevert` | `0x44792f25` | -| `initialize` | `0xbf2c8539` | -| `isApproved` | `0xa389783e` | -| `isCreditSpendable` | `0x4f8990fd` | -| `isNonceUsed` | `0xcab7e8eb` | -| `onRequestComplete` | `0x5ed1f959` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestBlockedCredits` | `0xb62d25ac` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `sbType` | `0x745de344` | -| `setFeesPlug` | `0xd6a9a8b7` | -| `setFeesPool` | `0xd6684588` | -| `tokenOnChainBalances` | `0x3b27866d` | -| `transferCredits` | `0xf1686c89` | -| `transferOwnership` | `0xf2fde38b` | -| `unblockAndAssignCredits` | `0x01958181` | -| `unblockCredits` | `0xa0b32314` | -| `unwrap` | `0x7647691d` | -| `userCredits` | `0x20babb92` | -| `watcher__` | `0x300bb063` | -| `withdrawCredits` | `0xcfc6dbd9` | -| `wrap` | `0x023276f0` | +| `approveAppGateways` | `0x86d23ab2` | +| `asyncDeployer__` | `0x2a39e801` | +| `blockCredits` | `0x9e434307` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deployForwarder__` | `0xd4e3b034` | +| `deposit` | `0x5671d329` | +| `evmxSlug` | `0x8bae77c2` | +| `feesManager__` | `0x70568b58` | +| `feesPlugs` | `0x23f5ee8a` | +| `feesPool` | `0x6b259690` | +| `getAvailableCredits` | `0xb065a8e5` | +| `handleRevert` | `0x44792f25` | +| `initialize` | `0xbf2c8539` | +| `isApproved` | `0xa389783e` | +| `isCreditSpendable` | `0x4f8990fd` | +| `isNonceUsed` | `0xcab7e8eb` | +| `onRequestComplete` | `0x5ed1f959` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestBlockedCredits` | `0xb62d25ac` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `sbType` | `0x745de344` | +| `setFeesPlug` | `0xd6a9a8b7` | +| `setFeesPool` | `0xd6684588` | +| `tokenOnChainBalances` | `0x3b27866d` | +| `transferCredits` | `0xf1686c89` | +| `transferOwnership` | `0xf2fde38b` | +| `unblockAndAssignCredits` | `0x01958181` | +| `unblockCredits` | `0xa0b32314` | +| `unwrap` | `0x7647691d` | +| `userCredits` | `0x20babb92` | +| `watcher__` | `0x300bb063` | +| `withdrawCredits` | `0xcfc6dbd9` | +| `wrap` | `0x023276f0` | ## FeesPool -| Function | Signature | -| ---------------------------- | ------------ | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `getBalance` | `0x12065fe0` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `getBalance` | `0x12065fe0` | +| `grantRole` | `0x2f2ff15d` | +| `hasRole` | `0x91d14854` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `revokeRole` | `0xd547741f` | -| `transferOwnership` | `0xf2fde38b` | -| `withdraw` | `0xf3fef3a3` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `revokeRole` | `0xd547741f` | +| `transferOwnership` | `0xf2fde38b` | +| `withdraw` | `0xf3fef3a3` | ## AddressResolver -| Function | Signature | -| ---------------------------- | ------------ | -| `asyncDeployer__` | `0x2a39e801` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `contractAddresses` | `0xf689e892` | -| `defaultAuctionManager` | `0x8f27cdc6` | -| `deployForwarder__` | `0xd4e3b034` | -| `feesManager__` | `0x70568b58` | -| `initialize` | `0xc4d66de8` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `asyncDeployer__` | `0x2a39e801` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `contractAddresses` | `0xf689e892` | +| `defaultAuctionManager` | `0x8f27cdc6` | +| `deployForwarder__` | `0xd4e3b034` | +| `feesManager__` | `0x70568b58` | +| `initialize` | `0xc4d66de8` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `setAsyncDeployer` | `0xcb0ffff8` | -| `setContractAddress` | `0xe001f841` | -| `setDefaultAuctionManager` | `0xede8b4b5` | -| `setDeployForwarder` | `0xaeaee8a6` | -| `setFeesManager` | `0x1c89382a` | -| `setWatcher` | `0x24f48bc5` | -| `transferOwnership` | `0xf2fde38b` | -| `watcher__` | `0x300bb063` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `setAsyncDeployer` | `0xcb0ffff8` | +| `setContractAddress` | `0xe001f841` | +| `setDefaultAuctionManager` | `0xede8b4b5` | +| `setDeployForwarder` | `0xaeaee8a6` | +| `setFeesManager` | `0x1c89382a` | +| `setWatcher` | `0x24f48bc5` | +| `transferOwnership` | `0xf2fde38b` | +| `watcher__` | `0x300bb063` | ## AsyncDeployer -| Function | Signature | -| ------------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `asyncDeployer__` | `0x2a39e801` | -| `asyncPromiseBeacon` | `0xc0fbc0ef` | -| `asyncPromiseCounter` | `0x97cdbf4c` | -| `asyncPromiseImplementation` | `0x59531b8d` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `deployAsyncPromiseContract` | `0x9851be0b` | -| `deployForwarder__` | `0xd4e3b034` | -| `feesManager__` | `0x70568b58` | -| `forwarderBeacon` | `0x945709ae` | -| `forwarderImplementation` | `0xe38d60a1` | -| `getAsyncPromiseAddress` | `0x104f39b4` | -| `getForwarderAddress` | `0x9c038b01` | -| `getOrDeployForwarderContract` | `0xe9bf1edf` | -| `initialize` | `0x485cc955` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | +| Function | Signature | +| -------- | --------- | +| `addressResolver__` | `0x6a750469` | +| `asyncDeployer__` | `0x2a39e801` | +| `asyncPromiseBeacon` | `0xc0fbc0ef` | +| `asyncPromiseCounter` | `0x97cdbf4c` | +| `asyncPromiseImplementation` | `0x59531b8d` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deployAsyncPromiseContract` | `0x9851be0b` | +| `deployForwarder__` | `0xd4e3b034` | +| `feesManager__` | `0x70568b58` | +| `forwarderBeacon` | `0x945709ae` | +| `forwarderImplementation` | `0xe38d60a1` | +| `getAsyncPromiseAddress` | `0x104f39b4` | +| `getForwarderAddress` | `0x9c038b01` | +| `getOrDeployForwarderContract` | `0xe9bf1edf` | +| `initialize` | `0x485cc955` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | | `setAsyncPromiseImplementation` | `0xeb506eab` | -| `setForwarderImplementation` | `0x83b1e974` | -| `transferOwnership` | `0xf2fde38b` | -| `watcher__` | `0x300bb063` | +| `setForwarderImplementation` | `0x83b1e974` | +| `transferOwnership` | `0xf2fde38b` | +| `watcher__` | `0x300bb063` | ## AsyncPromise -| Function | Signature | -| ------------------- | ------------ | +| Function | Signature | +| -------- | --------- | | `addressResolver__` | `0x6a750469` | -| `asyncDeployer__` | `0x2a39e801` | -| `callbackData` | `0xef44c272` | -| `callbackSelector` | `0x2764f92f` | +| `asyncDeployer__` | `0x2a39e801` | +| `callbackData` | `0xef44c272` | +| `callbackSelector` | `0x2764f92f` | | `deployForwarder__` | `0xd4e3b034` | -| `exceededMaxCopy` | `0xaf598c7c` | -| `feesManager__` | `0x70568b58` | -| `initialize` | `0x0ece6089` | -| `localInvoker` | `0x45eb87f4` | +| `exceededMaxCopy` | `0xaf598c7c` | +| `feesManager__` | `0x70568b58` | +| `initialize` | `0x0ece6089` | +| `localInvoker` | `0x45eb87f4` | | `markOnchainRevert` | `0xd0e7af1b` | -| `markResolved` | `0x822d5d1f` | -| `requestCount` | `0x5badbe4c` | -| `rescueFunds` | `0x6ccae054` | -| `returnData` | `0xebddbaf6` | -| `state` | `0xc19d93fb` | -| `then` | `0x0bf2ba15` | -| `watcher__` | `0x300bb063` | +| `markResolved` | `0x822d5d1f` | +| `requestCount` | `0x5badbe4c` | +| `rescueFunds` | `0x6ccae054` | +| `returnData` | `0xebddbaf6` | +| `state` | `0xc19d93fb` | +| `then` | `0x0bf2ba15` | +| `watcher__` | `0x300bb063` | ## DeployForwarder -| Function | Signature | -| ---------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `asyncDeployer__` | `0x2a39e801` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `deploy` | `0x940f11af` | -| `deployForwarder__` | `0xd4e3b034` | -| `deployerSwitchboardType` | `0xaa381f9a` | -| `feesManager__` | `0x70568b58` | -| `initialize` | `0x6133f985` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `addressResolver__` | `0x6a750469` | +| `asyncDeployer__` | `0x2a39e801` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deploy` | `0x940f11af` | +| `deployForwarder__` | `0xd4e3b034` | +| `deployerSwitchboardType` | `0xaa381f9a` | +| `feesManager__` | `0x70568b58` | +| `initialize` | `0x6133f985` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `saltCounter` | `0xa04c6809` | -| `transferOwnership` | `0xf2fde38b` | -| `watcher__` | `0x300bb063` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `saltCounter` | `0xa04c6809` | +| `transferOwnership` | `0xf2fde38b` | +| `watcher__` | `0x300bb063` | ## Forwarder -| Function | Signature | -| ------------------- | ------------ | +| Function | Signature | +| -------- | --------- | | `addressResolver__` | `0x6a750469` | -| `asyncDeployer__` | `0x2a39e801` | -| `chainSlug` | `0xb349ba65` | +| `asyncDeployer__` | `0x2a39e801` | +| `chainSlug` | `0xb349ba65` | | `deployForwarder__` | `0xd4e3b034` | -| `feesManager__` | `0x70568b58` | -| `getChainSlug` | `0x0b8c6568` | +| `feesManager__` | `0x70568b58` | +| `getChainSlug` | `0x0b8c6568` | | `getOnChainAddress` | `0x9da48789` | -| `initialize` | `0x148841cb` | -| `onChainAddress` | `0x8bd0b363` | -| `rescueFunds` | `0x6ccae054` | -| `watcher__` | `0x300bb063` | +| `initialize` | `0x148841cb` | +| `onChainAddress` | `0x8bd0b363` | +| `rescueFunds` | `0x6ccae054` | +| `watcher__` | `0x300bb063` | ## ForwarderSolana @@ -315,263 +315,264 @@ ## ProxyFactory -| Function | Signature | -| ----------------------------- | ------------ | -| `adminOf` | `0x2abbef15` | -| `changeAdmin` | `0x1acfd02a` | -| `deploy` | `0x545e7c61` | -| `deployAndCall` | `0x4314f120` | -| `deployDeterministic` | `0x3729f922` | -| `deployDeterministicAndCall` | `0xa97b90d5` | -| `initCodeHash` | `0xdb4c545e` | +| Function | Signature | +| -------- | --------- | +| `adminOf` | `0x2abbef15` | +| `changeAdmin` | `0x1acfd02a` | +| `deploy` | `0x545e7c61` | +| `deployAndCall` | `0x4314f120` | +| `deployDeterministic` | `0x3729f922` | +| `deployDeterministicAndCall` | `0xa97b90d5` | +| `initCodeHash` | `0xdb4c545e` | | `predictDeterministicAddress` | `0x5414dff0` | -| `upgrade` | `0x99a88ec4` | -| `upgradeAndCall` | `0x9623609d` | +| `upgrade` | `0x99a88ec4` | +| `upgradeAndCall` | `0x9623609d` | ## TestUSDC -| Function | Signature | -| ------------------ | ------------ | +| Function | Signature | +| -------- | --------- | | `DOMAIN_SEPARATOR` | `0x3644e515` | -| `allowance` | `0xdd62ed3e` | -| `approve` | `0x095ea7b3` | -| `balanceOf` | `0x70a08231` | -| `decimals` | `0x313ce567` | -| `mint` | `0x40c10f19` | -| `name` | `0x06fdde03` | -| `nonces` | `0x7ecebe00` | -| `owner` | `0x8da5cb5b` | -| `permit` | `0xd505accf` | -| `symbol` | `0x95d89b41` | -| `totalSupply` | `0x18160ddd` | -| `transfer` | `0xa9059cbb` | -| `transferFrom` | `0x23b872dd` | +| `allowance` | `0xdd62ed3e` | +| `approve` | `0x095ea7b3` | +| `balanceOf` | `0x70a08231` | +| `decimals` | `0x313ce567` | +| `mint` | `0x40c10f19` | +| `name` | `0x06fdde03` | +| `nonces` | `0x7ecebe00` | +| `owner` | `0x8da5cb5b` | +| `permit` | `0xd505accf` | +| `symbol` | `0x95d89b41` | +| `totalSupply` | `0x18160ddd` | +| `transfer` | `0xa9059cbb` | +| `transferFrom` | `0x23b872dd` | ## ContractFactoryPlug -| Function | Signature | -| ---------------------------- | ------------ | -| `appGatewayId` | `0x1c335f49` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `connectSocket` | `0x258d19c8` | -| `deployContract` | `0xa0695389` | -| `getAddress` | `0x94ca2cb5` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | -| `initSocket` | `0xa07d8545` | -| `isSocketInitialized` | `0x9a7d9a9b` | -| `overrides` | `0x4a85f041` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `appGatewayId` | `0x1c335f49` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `connectSocket` | `0x258d19c8` | +| `deployContract` | `0xa0695389` | +| `getAddress` | `0x94ca2cb5` | +| `grantRole` | `0x2f2ff15d` | +| `hasRole` | `0x91d14854` | +| `initSocket` | `0xa07d8545` | +| `isSocketInitialized` | `0x9a7d9a9b` | +| `overrides` | `0x4a85f041` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `revokeRole` | `0xd547741f` | -| `socket__` | `0xc6a261d2` | -| `transferOwnership` | `0xf2fde38b` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `revokeRole` | `0xd547741f` | +| `socket__` | `0xc6a261d2` | +| `transferOwnership` | `0xf2fde38b` | ## Configurations -| Function | Signature | -| ---------------------------- | ------------ | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `getPlugConfigs` | `0x25945c1a` | -| `initialize` | `0x485cc955` | -| `isValidPlug` | `0x00f9b9f4` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `getPlugConfigs` | `0x25945c1a` | +| `initialize` | `0x485cc955` | +| `isValidPlug` | `0x00f9b9f4` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `setAppGatewayConfigs` | `0xebfb22cd` | -| `setIsValidPlug` | `0x4842c37a` | -| `setSocket` | `0x38d4de67` | -| `setSwitchboard` | `0x491eac1f` | -| `sockets` | `0xb44a23ab` | -| `switchboards` | `0xaa539546` | -| `transferOwnership` | `0xf2fde38b` | -| `verifyConnections` | `0x36cb19fb` | -| `watcher__` | `0x300bb063` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `setAppGatewayConfigs` | `0xebfb22cd` | +| `setIsValidPlug` | `0x4842c37a` | +| `setSocket` | `0x38d4de67` | +| `setSwitchboard` | `0x491eac1f` | +| `sockets` | `0xb44a23ab` | +| `switchboards` | `0xaa539546` | +| `transferOwnership` | `0xf2fde38b` | +| `verifyConnections` | `0x36cb19fb` | +| `watcher__` | `0x300bb063` | ## PromiseResolver -| Function | Signature | -| ----------------- | ------------ | -| `markRevert` | `0x56501015` | -| `rescueFunds` | `0x6ccae054` | +| Function | Signature | +| -------- | --------- | +| `markRevert` | `0x56501015` | +| `rescueFunds` | `0x6ccae054` | | `resolvePromises` | `0xbf8484b8` | -| `watcher__` | `0x300bb063` | +| `watcher__` | `0x300bb063` | ## RequestHandler -| Function | Signature | -| ------------------------------ | ------------ | -| `addressResolver__` | `0x6a750469` | -| `assignTransmitter` | `0xae5e9c48` | -| `asyncDeployer__` | `0x2a39e801` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `cancelRequest` | `0x3b5fd6fb` | -| `cancelRequestForReverts` | `0x82970278` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `deployForwarder__` | `0xd4e3b034` | -| `feesManager__` | `0x70568b58` | -| `getBatchPayloadIds` | `0xfd83cd1f` | -| `getPayload` | `0xb48fd0fe` | -| `getPrecompileFees` | `0xabac263c` | -| `getRequest` | `0xcf39abf6` | -| `getRequestBatchIds` | `0xe138fadb` | -| `handleRevert` | `0xcc88d3f9` | -| `increaseFees` | `0x10205541` | -| `initialize` | `0x485cc955` | -| `nextBatchCount` | `0x333a3963` | -| `nextRequestCount` | `0xfef72893` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `payloadCounter` | `0x550ce1d5` | -| `precompiles` | `0x9932450b` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `setPrecompile` | `0x122e0042` | -| `setRequestPayloadCountLimit` | `0x8526582b` | -| `submitRequest` | `0xf91ba7cc` | -| `transferOwnership` | `0xf2fde38b` | +| Function | Signature | +| -------- | --------- | +| `addressResolver__` | `0x6a750469` | +| `assignTransmitter` | `0xae5e9c48` | +| `asyncDeployer__` | `0x2a39e801` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `cancelRequest` | `0x3b5fd6fb` | +| `cancelRequestForReverts` | `0x82970278` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `deployForwarder__` | `0xd4e3b034` | +| `feesManager__` | `0x70568b58` | +| `getBatchPayloadIds` | `0xfd83cd1f` | +| `getPayload` | `0xb48fd0fe` | +| `getPrecompileFees` | `0xabac263c` | +| `getRequest` | `0xcf39abf6` | +| `getRequestBatchIds` | `0xe138fadb` | +| `handleRevert` | `0xcc88d3f9` | +| `increaseFees` | `0x10205541` | +| `initialize` | `0x485cc955` | +| `nextBatchCount` | `0x333a3963` | +| `nextRequestCount` | `0xfef72893` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `payloadCounter` | `0x550ce1d5` | +| `precompiles` | `0x9932450b` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `setPrecompile` | `0x122e0042` | +| `setRequestPayloadCountLimit` | `0x8526582b` | +| `submitRequest` | `0xf91ba7cc` | +| `transferOwnership` | `0xf2fde38b` | | `updateRequestAndProcessBatch` | `0x46464471` | -| `watcher__` | `0x300bb063` | +| `watcher__` | `0x300bb063` | ## Watcher -| Function | Signature | -| ---------------------------- | ------------ | -| `addressResolver__` | `0x6a750469` | -| `appGatewayTemp` | `0x1394c029` | -| `asyncDeployer__` | `0x2a39e801` | -| `callAppGateways` | `0x0050bef1` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `cancelRequest` | `0x50ad0779` | -| `clearQueue` | `0xf22cb874` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `configurations__` | `0x52a3bbeb` | -| `deployForwarder__` | `0xd4e3b034` | -| `evmxSlug` | `0x8bae77c2` | -| `feesManager__` | `0x70568b58` | -| `getCurrentRequestCount` | `0x5715abbb` | -| `getPayloadParams` | `0xae5eeb77` | -| `getPrecompileFees` | `0xabac263c` | -| `getRequestParams` | `0x71263d0d` | -| `increaseFees` | `0xe9b304da` | -| `initialize` | `0xaaf7fc1a` | -| `isAppGatewayCalled` | `0xa79da6c7` | -| `isNonceUsed` | `0x5d00bb12` | -| `isWatcher` | `0x84785ecd` | -| `latestAsyncPromise` | `0xb8a8ba52` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `addressResolver__` | `0x6a750469` | +| `appGatewayTemp` | `0x1394c029` | +| `asyncDeployer__` | `0x2a39e801` | +| `callAppGateways` | `0x0050bef1` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `cancelRequest` | `0x50ad0779` | +| `clearQueue` | `0xf22cb874` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `configurations__` | `0x52a3bbeb` | +| `deployForwarder__` | `0xd4e3b034` | +| `evmxSlug` | `0x8bae77c2` | +| `feesManager__` | `0x70568b58` | +| `getCurrentRequestCount` | `0x5715abbb` | +| `getPayloadParams` | `0xae5eeb77` | +| `getPrecompileFees` | `0xabac263c` | +| `getRequestParams` | `0x71263d0d` | +| `increaseFees` | `0xe9b304da` | +| `initialize` | `0xaaf7fc1a` | +| `isAppGatewayCalled` | `0xa79da6c7` | +| `isNonceUsed` | `0x5d00bb12` | +| `isWatcher` | `0x84785ecd` | +| `latestAsyncPromise` | `0xb8a8ba52` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `payloadQueue` | `0x74f00ffb` | -| `promiseResolver__` | `0xdee152be` | -| `queue` | `0x65967f1a` | -| `queueAndSubmit` | `0x9d4c9df7` | -| `renounceOwnership` | `0x715018a6` | -| `requestHandler__` | `0x55184561` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0xa58c6fc5` | -| `setCoreContracts` | `0xefa891c4` | -| `setIsValidPlug` | `0x06c0a40a` | -| `setTriggerFees` | `0xaeb30511` | -| `submitRequest` | `0x4890b5ef` | -| `transferOwnership` | `0xf2fde38b` | -| `triggerFees` | `0x73f76aec` | -| `triggerFromChainSlug` | `0xd12b4f12` | -| `triggerFromPlug` | `0x3b847d12` | -| `watcherMultiCall` | `0x8021e82b` | -| `watcher__` | `0x300bb063` | +| `payloadQueue` | `0x74f00ffb` | +| `promiseResolver__` | `0xdee152be` | +| `queue` | `0x65967f1a` | +| `queueAndSubmit` | `0x9d4c9df7` | +| `renounceOwnership` | `0x715018a6` | +| `requestHandler__` | `0x55184561` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0xa58c6fc5` | +| `setCoreContracts` | `0xefa891c4` | +| `setIsValidPlug` | `0x06c0a40a` | +| `setTriggerFees` | `0xaeb30511` | +| `submitRequest` | `0x4890b5ef` | +| `transferOwnership` | `0xf2fde38b` | +| `triggerFees` | `0x73f76aec` | +| `triggerFromChainSlug` | `0xd12b4f12` | +| `triggerFromPlug` | `0x3b847d12` | +| `watcherMultiCall` | `0x8021e82b` | +| `watcher__` | `0x300bb063` | ## FastSwitchboard -| Function | Signature | -| ---------------------------- | ------------ | -| `allowPayload` | `0x31c23f66` | -| `attest` | `0x63671b60` | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `chainSlug` | `0xb349ba65` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `grantRole` | `0x2f2ff15d` | -| `hasRole` | `0x91d14854` | -| `isAttested` | `0xc13c2396` | -| `owner` | `0x8da5cb5b` | +| Function | Signature | +| -------- | --------- | +| `allowPayload` | `0x31c23f66` | +| `attest` | `0x63671b60` | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `chainSlug` | `0xb349ba65` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `grantRole` | `0x2f2ff15d` | +| `hasRole` | `0x91d14854` | +| `isAttested` | `0xc13c2396` | +| `owner` | `0x8da5cb5b` | | `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `registerSwitchboard` | `0x74f5b1fc` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `revokeRole` | `0xd547741f` | -| `socket__` | `0xc6a261d2` | -| `transferOwnership` | `0xf2fde38b` | +| `registerSwitchboard` | `0x74f5b1fc` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `revokeRole` | `0xd547741f` | +| `socket__` | `0xc6a261d2` | +| `transferOwnership` | `0xf2fde38b` | ## ReadPrecompile -| Function | Signature | -| ------------------------------ | ------------ | -| `expiryTime` | `0x99bc0aea` | -| `getPrecompileFees` | `0xb7a3d04c` | -| `handlePayload` | `0x1d5e1d98` | -| `readFees` | `0xe06357a2` | -| `rescueFunds` | `0x6ccae054` | -| `resolvePayload` | `0xea92e825` | -| `setExpiryTime` | `0x30fc4cff` | -| `setFees` | `0x3d18678e` | +| Function | Signature | +| -------- | --------- | +| `expiryTime` | `0x99bc0aea` | +| `getPrecompileFees` | `0xb7a3d04c` | +| `handlePayload` | `0x1d5e1d98` | +| `readFees` | `0xe06357a2` | +| `rescueFunds` | `0x6ccae054` | +| `resolvePayload` | `0xea92e825` | +| `setExpiryTime` | `0x30fc4cff` | +| `setFees` | `0x3d18678e` | | `validateAndGetPrecompileData` | `0x997f5bef` | -| `watcher__` | `0x300bb063` | +| `watcher__` | `0x300bb063` | ## SchedulePrecompile -| Function | Signature | -| ------------------------------ | ------------ | -| `expiryTime` | `0x99bc0aea` | -| `getPrecompileFees` | `0xb7a3d04c` | -| `handlePayload` | `0x1d5e1d98` | -| `maxScheduleDelayInSeconds` | `0x3ef01cdb` | -| `rescueFunds` | `0x6ccae054` | -| `resolvePayload` | `0xea92e825` | -| `scheduleCallbackFees` | `0x4c5b6007` | -| `scheduleFeesPerSecond` | `0x852a74c1` | -| `setExpiryTime` | `0x30fc4cff` | +| Function | Signature | +| -------- | --------- | +| `expiryTime` | `0x99bc0aea` | +| `getPrecompileFees` | `0xb7a3d04c` | +| `handlePayload` | `0x1d5e1d98` | +| `maxScheduleDelayInSeconds` | `0x3ef01cdb` | +| `rescueFunds` | `0x6ccae054` | +| `resolvePayload` | `0xea92e825` | +| `scheduleCallbackFees` | `0x4c5b6007` | +| `scheduleFeesPerSecond` | `0x852a74c1` | +| `setExpiryTime` | `0x30fc4cff` | | `setMaxScheduleDelayInSeconds` | `0x12953318` | -| `setScheduleCallbackFees` | `0xec8fd71e` | -| `setScheduleFeesPerSecond` | `0x28e59e57` | +| `setScheduleCallbackFees` | `0xec8fd71e` | +| `setScheduleFeesPerSecond` | `0x28e59e57` | | `validateAndGetPrecompileData` | `0x997f5bef` | -| `watcher__` | `0x300bb063` | +| `watcher__` | `0x300bb063` | ## WritePrecompile -| Function | Signature | -| ------------------------------ | ------------ | -| `cancelOwnershipHandover` | `0x54d1f13d` | -| `chainMaxMsgValueLimit` | `0x01d1e126` | -| `completeOwnershipHandover` | `0xf04e283e` | -| `contractFactoryPlugs` | `0x35426631` | -| `digestHashes` | `0xd1a862bf` | -| `expiryTime` | `0x99bc0aea` | -| `getDigest` | `0x91b6288b` | -| `getPrecompileFees` | `0xb7a3d04c` | -| `getPrevBatchDigestHash` | `0x372863a1` | -| `handlePayload` | `0x1d5e1d98` | -| `initialize` | `0xeb990c59` | -| `owner` | `0x8da5cb5b` | -| `ownershipHandoverExpiresAt` | `0xfee81cf4` | -| `renounceOwnership` | `0x715018a6` | -| `requestOwnershipHandover` | `0x25692962` | -| `rescueFunds` | `0x6ccae054` | -| `resolvePayload` | `0xea92e825` | -| `setContractFactoryPlugs` | `0x8b198f5c` | -| `setExpiryTime` | `0x30fc4cff` | -| `setFees` | `0x3d18678e` | -| `transferOwnership` | `0xf2fde38b` | +| Function | Signature | +| -------- | --------- | +| `cancelOwnershipHandover` | `0x54d1f13d` | +| `chainMaxMsgValueLimit` | `0x01d1e126` | +| `completeOwnershipHandover` | `0xf04e283e` | +| `contractFactoryPlugs` | `0x35426631` | +| `digestHashes` | `0xd1a862bf` | +| `expiryTime` | `0x99bc0aea` | +| `getDigest` | `0x91b6288b` | +| `getPrecompileFees` | `0xb7a3d04c` | +| `getPrevBatchDigestHash` | `0x372863a1` | +| `handlePayload` | `0x1d5e1d98` | +| `initialize` | `0xeb990c59` | +| `owner` | `0x8da5cb5b` | +| `ownershipHandoverExpiresAt` | `0xfee81cf4` | +| `renounceOwnership` | `0x715018a6` | +| `requestOwnershipHandover` | `0x25692962` | +| `rescueFunds` | `0x6ccae054` | +| `resolvePayload` | `0xea92e825` | +| `setContractFactoryPlugs` | `0x8b198f5c` | +| `setExpiryTime` | `0x30fc4cff` | +| `setFees` | `0x3d18678e` | +| `transferOwnership` | `0xf2fde38b` | | `updateChainMaxMsgValueLimits` | `0x6a7aa6ac` | -| `uploadProof` | `0x81b48fcf` | +| `uploadProof` | `0x81b48fcf` | | `validateAndGetPrecompileData` | `0x997f5bef` | -| `watcherProofs` | `0x3fa3166b` | -| `watcher__` | `0x300bb063` | -| `writeFees` | `0x5c664aeb` | +| `watcherProofs` | `0x3fa3166b` | +| `watcher__` | `0x300bb063` | +| `writeFees` | `0x5c664aeb` | + diff --git a/contracts/protocol/SocketConfig.sol b/contracts/protocol/SocketConfig.sol index 32bafb17..7b2fa463 100644 --- a/contracts/protocol/SocketConfig.sol +++ b/contracts/protocol/SocketConfig.sol @@ -59,6 +59,12 @@ abstract contract SocketConfig is ISocket, AccessControl { isValidSwitchboard[switchboard_] = SwitchboardStatus.DISABLED; emit SwitchboardDisabled(switchboard_); } +// --- start here + // TODO: ask why enableSwitchboard is only callable by switchboard (as switchboard address is taken from + // msg.sender) also this account must have GOVERNANCE_ROLE role, + // which is not intuitive for switchboard to have this role. + // On the other hand, disableSwitchboard is callable by account with SWITCHBOARD_DISABLER_ROLE role. + // Also registerSwitchboard is only callable by switchboard with no role check. // @notice function to enable a switchboard // @dev only callable by governance role diff --git a/foundry.toml b/foundry.toml index 9b8a496e..72646097 100644 --- a/foundry.toml +++ b/foundry.toml @@ -10,29 +10,29 @@ evm_version = 'paris' via_ir = false [labels] -0xE486D1Da5E250a268b5DA7eBc6AF3503bf8288DC = "AddressResolver" -0xf0CaA8D7b0d7be914D1801Ab2d95B1A75fCd4c01 = "AddressResolverImpl" -0x66d59d0bA711241785F1110C163448071c4Dd4Cc = "AsyncDeployer" -0xFe468C6506c212734A998848Da419E8041f47A64 = "AsyncDeployerImpl" -0xD2E1bF41B54ada70590873BE2409d5AD20341E82 = "AuctionManager" -0xfDDC7488C060Bf56107ca3A47918aEf06B59071d = "AuctionManagerImpl" -0x63cbD05da9bE1e37D3fC3eeB27da601FBbA199d1 = "Configurations" -0x36d42DFE5b2aeF21F7750947cEDc8c950f89fC6A = "ConfigurationsImpl" -0x1746948725fc2E2003511c34BE3509FA112A392E = "DeployForwarder" -0x227d6082Ea2D7Ee52b33F0d83B9411297ABf8ecE = "DeployForwarderImpl" -0xc9B4874dDA3AbB27DF56466DD650dd9B30b48986 = "ERC1967Factory" -0x686Be563fa9797aa7F66dCF73321cbd2F7a98874 = "FeesManager" -0x17F2aC81c4f1bee70D4c8ee9a3cb28E4e096De4f = "FeesManagerImpl" +0xD57689FA7802194Adb2Df677fc01514e9f53A62C = "AddressResolver" +0xAAA7CED4aAc30C9ec66062708E322729941400D0 = "AddressResolverImpl" +0x71C64ECA3817C50BE5BD2ceAb44D9b583ee18D88 = "AsyncDeployer" +0xB9c3482d90cc93491250195F5971D4536e01b17c = "AsyncDeployerImpl" +0xC31b35262A5405Ae629abFE76eeaF49D64665890 = "AuctionManager" +0x5AF2B4685C941808cA798fd48DBb1feCaD40AC54 = "AuctionManagerImpl" +0x762379D7bcAa209aa56A3A68d62B8387Ebf36f58 = "Configurations" +0xB3B4b2a4e057E34462343a22eE1D8B05BEd1B206 = "ConfigurationsImpl" +0x7c7D4C71eFFd0f56a660b37c1B67EaE23BCb8A8c = "DeployForwarder" +0x1Eb1308Fa45F9a8B92BDB4B4Cb902CA74A18Dc3F = "DeployForwarderImpl" +0x011c9beC76F56071271553b4eC8B1d7e101FDe60 = "ERC1967Factory" +0xF942Fc4beE09256D90f6Ec309AB07479F4e9B0A8 = "FeesManager" +0x0Cd5E1be74d86EcD72A8698E3BcF1b5E9E0fC983 = "FeesManagerImpl" 0x9De353dD1131aB4e502590D3a1832652FA316268 = "FeesPool" -0xC570206ACBa112fC8d438235BF8cE31b3548aa96 = "ForwarderSolana" -0x3Fe4DDEd31943B90cB2AdcA39cE1f33Fa4Ab45d8 = "ForwarderSolanaImpl" -0xa0EEEC3D28acF5cF00181130aACf8A63d5F53F94 = "PromiseResolver" -0x78dFD826E6Aef751144987E1E25326De48879869 = "ReadPrecompile" -0xd183b5bf6d77E2D253a1230C2eAE34C18810Fbc3 = "RequestHandler" -0xD05D508704a8984a471E4FCdD57865D36c4A88B7 = "RequestHandlerImpl" -0xD832BFfE0c5e43Ef36dE8a2c50367d3f12DE09B7 = "SchedulePrecompile" -0x2d0fAB18450A7A2885a0130090BfA0B184F0b801 = "Watcher" -0xEEe9A8308b5b2A5814b35D10aeBCDeA760B9117f = "WatcherImpl" -0x1D179869F6079818a1DC852fEbf0890cc6DC558c = "WritePrecompile" -0x9461e4b96d5ceAa749E6Ee535979D1f3A83d5968 = "WritePrecompileImpl" -0xD3A6671C53c57133095b800a10024060a5B6Bf02 = "APP_GATEWAY" +0x9bBb97c66be06458A63D11ff6025Fdad104DA285 = "ForwarderSolana" +0x0811A7dDE3dBe181CEc492086d6A509E7f7a3752 = "ForwarderSolanaImpl" +0x04D0903142D9457fdfd92F971929B8784359De56 = "PromiseResolver" +0x0bcB79686eb404b9938d14d055B5fC9845b68449 = "ReadPrecompile" +0xBcbf0aF70c975F24cC74fdEE0785293F97EA235a = "RequestHandler" +0x43fC7DB472a86c6E720853Bf0dB0126A3fEa2726 = "RequestHandlerImpl" +0xafeDC2E94698C3DaE24dFdFfE77DB1fec1A86C62 = "SchedulePrecompile" +0x72Bc51B1EE2CcD200a82e042071B9F27A2bFDf45 = "Watcher" +0x767Eb57D785e574b9528a3c828F555a96A899310 = "WatcherImpl" +0x8B77Be1dd4bC0C52759902336844A7c440934E84 = "WritePrecompile" +0x4c4953DfDAA93F83d1c47999984C8b50ac5D6d46 = "WritePrecompileImpl" +0xDC81d1BE1E8d92C5a98a15D89A16134D74121FC2 = "APP_GATEWAY" diff --git a/hardhat-scripts/deploy/3.configureChains.ts b/hardhat-scripts/deploy/3.configureChains.ts index b99d158d..e3554bcc 100644 --- a/hardhat-scripts/deploy/3.configureChains.ts +++ b/hardhat-scripts/deploy/3.configureChains.ts @@ -205,7 +205,7 @@ export const whitelistToken = async ( await getInstance(Contracts.FeesPlug, feesPlugAddress) ).connect(signer); - const tokens = getFeeTokens(mode, chain); + const tokens = getFeeTokens(chain); if (tokens.length == 0) return; for (const token of tokens) { diff --git a/script/super-token-solana/DeployEVMSolanaApps.s.sol b/script/super-token-solana/DeployEVMSolanaApps.s.sol index cb080b09..4bfef359 100644 --- a/script/super-token-solana/DeployEVMSolanaApps.s.sol +++ b/script/super-token-solana/DeployEVMSolanaApps.s.sol @@ -23,7 +23,7 @@ contract DeployEVMSolanaApps is Script { // fill with correct values after deployment bytes32 solanaProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); - address forwarderSolanaAddress = 0xC570206ACBa112fC8d438235BF8cE31b3548aa96; + address forwarderSolanaAddress = 0x9bBb97c66be06458A63D11ff6025Fdad104DA285; // Setting fee payment on Arbitrum Sepolia uint256 fees = 10 ether; From 613467d1dae0ba004f1f2f405a03745feb676093 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Sat, 27 Sep 2025 10:26:18 +0200 Subject: [PATCH 17/18] Add comments and small rename in FeesPlug --- contracts/evmx/fees/Credit.sol | 12 ++++++++-- contracts/evmx/interfaces/IConfigurations.sol | 5 ++-- contracts/evmx/plugs/FeesPlug.sol | 24 ++++++++++--------- .../watcher/precompiles/WritePrecompile.sol | 4 ++-- contracts/protocol/base/PlugBase.sol | 3 +++ .../protocol/switchboard/SwitchboardBase.sol | 1 + 6 files changed, 32 insertions(+), 17 deletions(-) diff --git a/contracts/evmx/fees/Credit.sol b/contracts/evmx/fees/Credit.sol index dc4af589..93a0c55e 100644 --- a/contracts/evmx/fees/Credit.sol +++ b/contracts/evmx/fees/Credit.sol @@ -40,6 +40,7 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 52 /// @notice Mapping to track blocked credits for each user /// @dev address => userBlockedCredits + // TODO: this will have to be bytes32 (for solana addresses) mapping(address => uint256) public userBlockedCredits; // slot 53 @@ -53,12 +54,14 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 55 // token pool balances // chainSlug => token address => amount + // TODO: this will have to be bytes32 (for solana tokens) mapping(uint32 => mapping(address => uint256)) public tokenOnChainBalances; // slot 56 /// @notice Mapping to track nonce to whether it has been used /// @dev address => signatureNonce => isNonceUsed /// @dev used by watchers or other users in signatures + // TODO: how about this, do we need to change it to bytes32 ? - how nonces are used here ? mapping(address => mapping(uint256 => bool)) public isNonceUsed; // slot 57 @@ -140,13 +143,17 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew } /// @notice Deposits credits and native tokens to a user - /// @param depositTo_ The address to deposit the credits to + /// @param depositTo_ The EVMx address to deposit the credits to (it serves as accounting contract that mirrors real on-chain balances) /// @param chainSlug_ The chain slug /// @param token_ The token address /// @param nativeAmount_ The native amount /// @param creditAmount_ The credit amount + // TODO:4: for Solana handling we will need to have separate function deposit_solana() so that we do not have to change - (?) + // existing function on EVM UserVault which will refer to this one + // - also different handling on transmitter and indexer function deposit( uint32 chainSlug_, + // TODO: token will have to be bytes32 (for solana tokens) - this is the address of token on native chain (could be Solana) address token_, address depositTo_, uint256 nativeAmount_, @@ -155,10 +162,11 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew ) external override onlyWatcher { tokenOnChainBalances[chainSlug_][token_] += creditAmount_ + nativeAmount_; - // Mint tokens to the user + // Mint tokens to the some evmx accounting contract _mint(depositTo_, creditAmount_); if (nativeAmount_ > 0) { + // TODO: ask: what are native tokens in this context ? - native to real blockchains or to EVMx itself ? // if native transfer fails, add to credit bool success = feesPool.withdraw(depositTo_, nativeAmount_); diff --git a/contracts/evmx/interfaces/IConfigurations.sol b/contracts/evmx/interfaces/IConfigurations.sol index 227a18f3..49cc0bbf 100644 --- a/contracts/evmx/interfaces/IConfigurations.sol +++ b/contracts/evmx/interfaces/IConfigurations.sol @@ -33,9 +33,10 @@ interface IConfigurations { /// @return The socket function sockets(uint32 chainSlug_) external view returns (bytes32); - /// @notice Returns the socket for a given chain slug + /// @notice Returns the switchboardId for a given chain slug and switchboard type (1:1 mapping) /// @param chainSlug_ The chain slug - /// @return The socket + /// @param sbType_ The type of switchboard + /// @return switchboardId function switchboards(uint32 chainSlug_, bytes32 sbType_) external view returns (uint64); /// @notice Sets the switchboard for a network diff --git a/contracts/evmx/plugs/FeesPlug.sol b/contracts/evmx/plugs/FeesPlug.sol index b46baab1..e0b2d654 100644 --- a/contracts/evmx/plugs/FeesPlug.sol +++ b/contracts/evmx/plugs/FeesPlug.sol @@ -40,52 +40,54 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { /////////////////////// DEPOSIT AND WITHDRAWAL /////////////////////// function depositCredit( address token_, - address receiver_, + address evmx_receiver_, uint256 amount_, bytes memory data_ ) external override { - _deposit(token_, receiver_, amount_, 0, data_); + _deposit(token_, evmx_receiver_, amount_, 0, data_); } function depositCreditAndNative( address token_, - address receiver_, + address evmx_receiver_, uint256 amount_, bytes memory data_ ) external override { uint256 nativeAmount_ = amount_ / 10; - _deposit(token_, receiver_, amount_ - nativeAmount_, nativeAmount_, data_); + _deposit(token_, evmx_receiver_, amount_ - nativeAmount_, nativeAmount_, data_); } function depositToNative( address token_, - address receiver_, + address evmx_receiver_, uint256 amount_, bytes memory data_ ) external override { - _deposit(token_, receiver_, 0, amount_, data_); + _deposit(token_, evmx_receiver_, 0, amount_, data_); } /// @notice Deposits funds /// @param token_ The token address /// @param creditAmount_ The amount of fees /// @param nativeAmount_ The amount of native tokens - /// @param receiver_ The receiver address + /// @param evmx_receiver_ The evmx receiver address. EVMx tokens will be minted to this address to mirror real on-chain balances. function _deposit( address token_, - address receiver_, + address evmx_receiver_, uint256 creditAmount_, uint256 nativeAmount_, bytes memory data_ ) internal { if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); token_.safeTransferFrom(msg.sender, address(this), creditAmount_ + nativeAmount_); - emit FeesDeposited(token_, receiver_, creditAmount_, nativeAmount_, data_); + emit FeesDeposited(token_, evmx_receiver_, creditAmount_, nativeAmount_, data_); } - /// @notice Withdraws fees + /// @notice Withdraws fees - amount in is represented as 18 decimals token. + /// Before transferring we need to convert to do decimals given token has on this chain. + /// final_amount = input_amount / 10^18 * 10^decimals => input_amount * 10^(-18 + decimals) => input_amount * 10^(decimals - 18) /// @param token_ The token address - /// @param amount_ The amount + /// @param amount_ The input amount (represented as 18 decimals token) /// @param receiver_ The receiver address function withdrawFees( address token_, diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index fd747e5a..75d3a8d9 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -133,7 +133,7 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc queueParams_.overrideParams.writeFinality, queueParams_.overrideParams.gasLimit, queueParams_.overrideParams.value, - configurations__().switchboards( + configurations__().switchboards( // switchboardId queueParams_.transaction.chainSlug, queueParams_.switchboardType ) @@ -182,7 +182,7 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable, Watc payloadParams, transaction, appGateway, - transmitter_, + transmitter_, // TODO: we should use here Solana transmitter address prevBatchDigestHash, deadline, gasLimit, diff --git a/contracts/protocol/base/PlugBase.sol b/contracts/protocol/base/PlugBase.sol index a6eda992..1190d065 100644 --- a/contracts/protocol/base/PlugBase.sol +++ b/contracts/protocol/base/PlugBase.sol @@ -21,6 +21,9 @@ abstract contract PlugBase is IPlug { // overrides encoded in bytes bytes public overrides; + // TODO: why it does not have a switchboardId field if it is used in the _connectSocket function? + // should it not be saved is same way as appGatewayId? + // event emitted when plug is disconnected event ConnectorPlugDisconnected(); diff --git a/contracts/protocol/switchboard/SwitchboardBase.sol b/contracts/protocol/switchboard/SwitchboardBase.sol index 43d5cf52..c8a3bc1d 100644 --- a/contracts/protocol/switchboard/SwitchboardBase.sol +++ b/contracts/protocol/switchboard/SwitchboardBase.sol @@ -53,6 +53,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_ ) From 95fe49089e0994de09f17ab1ea51618990f1e745 Mon Sep 17 00:00:00 2001 From: Gregory The Dev Date: Sat, 27 Sep 2025 10:49:29 +0200 Subject: [PATCH 18/18] Minor fixes from CodeRabbit to: BorshEncoder.sol, EvmSolanaOnchainCalls.sol --- contracts/evmx/watcher/borsh-serde/BorshEncoder.sol | 11 +++++++++-- script/super-token-solana/EvmSolanaOnchainCalls.s.sol | 11 +++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol b/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol index b498059e..e0b0f9cb 100644 --- a/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol +++ b/contracts/evmx/watcher/borsh-serde/BorshEncoder.sol @@ -39,7 +39,7 @@ library BorshEncoder { uint128 arg = uint128(abiDecodedArg); bytes16 borshEncodedArg = encodeU128(arg); functionArgsPacked = abi.encodePacked(functionArgsPacked, borshEncodedArg); - } else if (keccak256(bytes(typeName)) == keccak256(bytes("string"))) { + } 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); @@ -65,7 +65,7 @@ library BorshEncoder { 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[]"))) { + } 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); @@ -154,6 +154,7 @@ library BorshEncoder { /// 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)); } @@ -165,31 +166,37 @@ library BorshEncoder { /** 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); } diff --git a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol index 4487f437..6bc06955 100644 --- a/script/super-token-solana/EvmSolanaOnchainCalls.s.sol +++ b/script/super-token-solana/EvmSolanaOnchainCalls.s.sol @@ -253,7 +253,7 @@ contract EvmSolanaOnchainCalls is Script { bytes32 solanaTargetProgramId = vm.envBytes32("SOLANA_TARGET_PROGRAM"); // May be subject to change - bytes32[] memory accounts = new bytes32[](5); + bytes32[] memory accounts = new bytes32[](4); // accounts 0 - plug signer pda : aprk6EUeRofYzyABHXLKG8hqJ3GHGV7Uhzbu612UMBD accounts[0] = 0x08aa478d7031ac31e2a406e25f1e4dbd00bce5cbd428b7bf5e5b01abfd4b7da8; // accounts 1 - trigger counter pda : 4EYucSHVdCt5BK9N95peyVCi7weQTBoW5Tf3a2nQbRbf @@ -334,7 +334,7 @@ contract EvmSolanaOnchainCalls is Script { // accounts 4 - token programId: TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA accounts[4] = 0x06ddf6e1d765a193d9cbe146ceeb79ac1cb485ed5f5b37913a8cf5857eff00a9; - bytes[] memory functionArguments = new bytes[](1); + bytes[] memory functionArguments = new bytes[](3); functionArguments[0] = abi.encode(order.srcAmount); uint256[] memory array = new uint256[](100); functionArguments[1] = abi.encode(array); @@ -346,12 +346,11 @@ contract EvmSolanaOnchainCalls is Script { }); functionArguments[2] = abi.encode(complexTestStruct); - string[] memory functionArgumentTypeNames = new string[](1); + string[] memory functionArgumentTypeNames = new string[](3); functionArgumentTypeNames[0] = "u64"; functionArgumentTypeNames[1] = "[u64;100]"; - functionArgumentTypeNames[ - 2 - ] = '{"ComplexTestStruct": {"name": "string","addr": "[u8;32]","isActive": "boolean","value": "u64"}}'; + functionArgumentTypeNames[2] = + '{"ComplexTestStruct": {"name": "string","addr": "[u8;32]","isActive": "boolean","value": "u64"}}'; bytes1[] memory accountFlags = new bytes1[](5); // superTokenConfigPda is not writable