Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
cfc1708
WIP
gwalen Jun 19, 2025
efbe256
Add ForwarderSolana and solana gatway and call scripts
gwalen Jun 20, 2025
c543ef9
refactor: add creating Solana style digest; emit debug events; fix de…
gwalen Jun 25, 2025
9fc61d3
refactor: WritePrecompile -> handlePayload() -> abi.decode for switch…
gwalen Jun 25, 2025
3afb4c8
Borsh encoding and decoding works with tests; first iteration
gwalen Jun 30, 2025
d718485
Fix decoding and always wrapping types into uint256
gwalen Jun 30, 2025
ade46e5
Add test for decoding Socket config account from Solana
gwalen Jun 30, 2025
38d756c
Simplify SolanaReadRequest - no need for keeping Generic shema inside…
gwalen Jun 30, 2025
6fd707c
Fix naming for BorshDecoder; add handling of Solana READ in the AppGa…
gwalen Jul 1, 2025
41e6f8a
Cleanup; Move Borsh encoder/decoder to borsh-serde directory
gwalen Jul 1, 2025
7003cd1
Comment to remove the onChainAddress_ from initialize() in ForwarderS…
gwalen Jul 2, 2025
549a766
fix: lost letter
gwalen Jul 3, 2025
86b8a29
feat: invoke trigger and read return value final changes
gwalen Jul 16, 2025
a09b06a
remove comments
gwalen Jul 16, 2025
a9510bc
Merge branch 'dev-solana-v2' into solana-triggers
gwalen Jul 17, 2025
701e866
CR fixes
gwalen Jul 17, 2025
baaf1fa
minor changes
gwalen Aug 27, 2025
ceb4cbd
Merge branch 'fees-deposit-hook' into solana-triggers
gwalen Sep 1, 2025
613467d
Add comments and small rename in FeesPlug
gwalen Sep 27, 2025
3b49f8e
Merge branch 'fees-deposit-hook' into solana-triggers
gwalen Sep 27, 2025
95fe490
Minor fixes from CodeRabbit to: BorshEncoder.sol, EvmSolanaOnchainCal…
gwalen Sep 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions contracts/evmx/base/AppGatewayBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,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_
Expand Down Expand Up @@ -221,6 +222,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);
Expand Down
12 changes: 10 additions & 2 deletions contracts/evmx/fees/Credit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -161,13 +164,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_,
Expand All @@ -177,10 +184,11 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew
if (!whitelistedReceivers[depositTo_]) revert InvalidReceiver();
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_);

Expand Down
94 changes: 94 additions & 0 deletions contracts/evmx/helpers/ForwarderSolana.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

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";
import {ForwarderStorage} from "./Forwarder.sol";

/// @title Forwarder Contract
/// @notice This contract acts as a forwarder for async calls to the on-chain contracts.
contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil {
error InvalidSolanaChainSlug();
error AddressResolverNotSet();

constructor() {
_disableInitializers(); // disable for implementation
}

/// @notice Initializer to replace constructor for upgradeable contracts
/// @param chainSlug_ chain slug on which the contract is deployed
//// @param onChainAddress_ on-chain address associated with this forwarder
/// @param addressResolver_ address resolver contract
function initialize(
uint32 chainSlug_,
bytes32 onChainAddress_, // TODO:GW: after demo remove this param, we take target as param in callSolana()
address addressResolver_
) public initializer {
if (chainSlug_ == CHAIN_SLUG_SOLANA_MAINNET || chainSlug_ == CHAIN_SLUG_SOLANA_DEVNET) {
chainSlug = chainSlug_;
} else {
revert InvalidSolanaChainSlug();
}
onChainAddress = onChainAddress_;
_setAddressResolver(addressResolver_);
}

/// @notice Returns the on-chain address associated with this forwarder.
/// @return The on-chain address.
function getOnChainAddress() external view returns (bytes32) {
return onChainAddress;
}

/// @notice Returns the chain slug on which the contract is deployed.
/// @return chain slug
function getChainSlug() external view returns (uint32) {
return chainSlug;
}

/// @notice 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, bytes32 target) external {
if (address(addressResolver__) == address(0)) {
revert AddressResolverNotSet();
}
if (address(watcher__()) == address(0)) {
revert WatcherNotSet();
}

// validates if the async modifier is set
address msgSender = msg.sender;
bool isAsyncModifierSet = IAppGateway(msgSender).isAsyncModifierSet();
if (!isAsyncModifierSet) revert AsyncModifierNotSet();

// fetch the override params from app gateway
(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
// address switchboard = watcherPrecompileConfig().switchboards(chainSlug, sbType);

// Queue the call in the middleware.
QueueParams memory queueParams;
queueParams.overrideParams = overrideParams;
queueParams.transaction = Transaction({
chainSlug: chainSlug,
// target: onChainAddress, // for Solana reads it should be accountToRead
// TODO: Solana forwarder can be a singleton - does not need to store onChainAddress and can use target as param
target: target,
payload: solanaPayload
});
queueParams.switchboardType = sbType;
watcher__().queue(queueParams, msgSender);
}
}
5 changes: 3 additions & 2 deletions contracts/evmx/interfaces/IConfigurations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
24 changes: 13 additions & 11 deletions contracts/evmx/plugs/FeesPlug.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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_,
Expand Down
3 changes: 3 additions & 0 deletions contracts/evmx/watcher/Configurations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,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
Expand Down
Loading