Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8032414
refactor FeesManager to handle solana - part 1
gwalen Sep 30, 2025
1387f9a
Add coments and rename variables
gwalen Sep 30, 2025
24c9e7b
Hardcode solana trasmitter key address in _createSolanaDigestParams()…
gwalen Oct 6, 2025
c101bf4
Fixes for deployment scripts; linter changes; deployment files
gwalen Oct 6, 2025
9128bac
changes to make socket counter example works
gwalen Oct 7, 2025
2564360
Add to foundry.toml yul optimizers to avoid stack-too-deep errors
gwalen Oct 8, 2025
06fde41
Add script to update fowarder solana and all necessary changes
gwalen Oct 8, 2025
0e73d38
Wip - add add events to log Configurations.verifyConnections(), add a…
gwalen Oct 9, 2025
26a45f4
Change FeePool addresss for local to dev (deployment); Add debug even…
gwalen Oct 10, 2025
9671282
Add Solana Evm libs to socket-protocol; modify FeesManager and Credit…
gwalen Oct 15, 2025
ff7a972
change setSusdcToken() to handle Solana; change IReceiver.onTransfer(…
gwalen Oct 16, 2025
14b528f
Fix createSusdcMintInstructionSolana(); add funding feeManager so tha…
gwalen Oct 17, 2025
bcf0479
Add FesPlugSolana setter for in FeesManager/Credit; add deployment sc…
gwalen Oct 18, 2025
0723eb4
Revert changes for testing Solana forwarder calls in isolation
gwalen Oct 18, 2025
d4af2bf
Wip deployment
gwalen Oct 22, 2025
ec7595d
fix: some changes to setup and deployment scripts: deployment trouble…
gwalen Oct 23, 2025
c197024
fix: hardcode the new transmitter Solana key in WritePrecompile; add …
gwalen Nov 10, 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ src/types
typechain-types/

.env
.env.*
.DS_Store

.gas-snapshot/
Expand Down
7 changes: 7 additions & 0 deletions Errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@
| `PromiseRevertFailed()` | `0x0175b9de` |
| `NotLatestPromise()` | `0x39ca95d3` |

## evmx/helpers/ForwarderSolana.sol

| Error | Signature |
| -------------------------- | ------------ |
| `InvalidSolanaChainSlug()` | `0xe37803ab` |
| `AddressResolverNotSet()` | `0x6d55276d` |

## evmx/plugs/ContractFactoryPlug.sol

| Error | Signature |
Expand Down
76 changes: 46 additions & 30 deletions EventTopics.md

Large diffs are not rendered by default.

410 changes: 337 additions & 73 deletions contracts/evmx/fees/Credit.sol

Large diffs are not rendered by default.

55 changes: 37 additions & 18 deletions contracts/evmx/fees/FeesManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.21;

import "./Credit.sol";
import {ForwarderSolana} from "../helpers/ForwarderSolana.sol";

/// @title FeesManager
/// @notice Contract for managing fees
Expand Down Expand Up @@ -53,17 +54,20 @@ contract FeesManager is Credit {
address feesPool_,
address owner_,
uint256 fees_,
bytes32 sbType_
bytes32 sbType_,
address forwarderSolana_
) public reinitializer(2) {
evmxSlug = evmxSlug_;
sbType = sbType_;
feesPool = IFeesPool(feesPool_);
susdcToken = _createContractId("susdc token");
susdcContractId = _createContractId("susdc token");
maxFeesPerChainSlug[evmxSlug_] = fees_;
_setMaxFees(fees_);

_initializeOwner(owner_);
_initializeAppGateway(addressResolver_);

forwarderSolana = ForwarderSolana(forwarderSolana_);
}

function setChainMaxFees(
Expand Down Expand Up @@ -94,56 +98,71 @@ contract FeesManager is Credit {

/////////////////////// FEES MANAGEMENT ///////////////////////

/// @notice Blocks fees for a request count
/// @notice Blocks fees for a request count - called when bidding starts (auction is started)
/// @param requestCount_ The batch identifier
/// @param consumeFrom_ The fees payer address
/// @param creditHolder_ The credit holder address (evmx address)
/// @param credits_ The total fees to block
/// @dev Only callable by delivery helper
function blockCredits(
uint40 requestCount_,
address consumeFrom_,
address creditHolder_,
uint256 credits_
) external override onlyRequestHandler {
if (balanceOf(consumeFrom_) < credits_) revert InsufficientCreditsAvailable();
if (balanceOf(creditHolder_) < credits_) revert InsufficientCreditsAvailable();

userBlockedCredits[consumeFrom_] += credits_;
blockedCredits[creditHolder_] += credits_;
requestBlockedCredits[requestCount_] = credits_;
emit CreditsBlocked(requestCount_, consumeFrom_, credits_);
emit CreditsBlocked(requestCount_, creditHolder_, credits_);
}
Comment on lines +101 to 116
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Ensure creditHolder_ matches request.consumeFrom to avoid accounting mismatches.

blockCredits writes to blockedCredits[creditHolder_] but unblock uses consumeFrom from request params. If they differ, unblock will underflow or corrupt balances. This should validate the invariant at block time.

 function blockCredits(
   uint40 requestCount_,
   address creditHolder_,
   uint256 credits_
 ) external override onlyRequestHandler {
-  if (balanceOf(creditHolder_) < credits_) revert InsufficientCreditsAvailable();
+  address consumeFrom = _getRequestParams(requestCount_).requestFeesDetails.consumeFrom;
+  if (creditHolder_ != consumeFrom) revert("creditHolder mismatch");
+  if (balanceOf(consumeFrom) < credits_) revert InsufficientCreditsAvailable();
-  blockedCredits[creditHolder_] += credits_;
+  blockedCredits[consumeFrom] += credits_;
   requestBlockedCredits[requestCount_] = credits_;
-  emit CreditsBlocked(requestCount_, creditHolder_, credits_);
+  emit CreditsBlocked(requestCount_, consumeFrom, credits_);
 }

Also applies to: 123-139

🤖 Prompt for AI Agents
In contracts/evmx/fees/FeesManager.sol around lines 101-116 (and similarly at
123-139), blockCredits currently records blockedCredits under creditHolder_ but
unblock uses request.consumeFrom later; add a validation at block time to ensure
creditHolder_ equals the request.consumeFrom address that will be used for
unblocking (or otherwise validate the request owner) so the same address is used
for both block and unblock operations. Concretely: fetch/derive the
request.consumeFrom (or require the caller to pass the same consumeFrom) and
require(creditHolder_ == consumeFrom) (or revert with a clear error) before
updating blockedCredits and requestBlockedCredits to prevent
underflow/accounting mismatches. Ensure the same invariant is added to the other
affected block/unblock site at lines 123-139.


/// @notice Unblocks fees after successful execution and assigns them to the transmitter
/// @notice Unblocks fees after successful execution and assigns them to the transmitter - this is called when request execution by transmitter is done.
/// Then we assign used credits to the transmitter (credits come from user and transmitter is making a call in his name) and unused credits are returned to the user.
/// @param requestCount_ The request count of the executed batch
/// @param assignTo_ The address of the transmitter
function unblockAndAssignCredits(
uint40 requestCount_,
address assignTo_
) external override onlyRequestHandler {
uint256 blockedCredits = requestBlockedCredits[requestCount_];
if (blockedCredits == 0) return;
uint256 blockedCreditsAmount = requestBlockedCredits[requestCount_];
if (blockedCreditsAmount == 0) return;

address consumeFrom = _getRequestParams(requestCount_).requestFeesDetails.consumeFrom;

// Unblock credits from the original user
userBlockedCredits[consumeFrom] -= blockedCredits;
blockedCredits[consumeFrom] -= blockedCreditsAmount;

// Burn tokens from the original user
_burn(consumeFrom, blockedCredits);
_burn(consumeFrom, blockedCreditsAmount);

// Mint tokens to the transmitter
_mint(assignTo_, blockedCredits);
_mint(assignTo_, blockedCreditsAmount);

// Clean up storage
delete requestBlockedCredits[requestCount_];
emit CreditsUnblockedAndAssigned(requestCount_, consumeFrom, assignTo_, blockedCredits);
emit CreditsUnblockedAndAssigned(
requestCount_,
consumeFrom,
assignTo_,
blockedCreditsAmount
);
}

function setFeesPlugSolanaProgramId(bytes32 feesPlugSolanaProgramId_) external onlyOwner {
feesPlugSolanaProgramId = feesPlugSolanaProgramId_;
}

function setSusdcSolanaProgramId(bytes32 susdcSolanaProgramId_) external onlyOwner {
susdcSolanaProgramId = susdcSolanaProgramId_;
}

/// @notice If there was an auction but no transmitter wanted to bid (auction was not finished), we unblock the credits and return them to the user
function unblockCredits(uint40 requestCount_) external override onlyRequestHandler {
uint256 blockedCredits = requestBlockedCredits[requestCount_];
if (blockedCredits == 0) return;
uint256 blockedCreditsAmount = requestBlockedCredits[requestCount_];
if (blockedCreditsAmount == 0) return;

// Unblock credits from the original user
address consumeFrom = _getRequestParams(requestCount_).requestFeesDetails.consumeFrom;
userBlockedCredits[consumeFrom] -= blockedCredits;
blockedCredits[consumeFrom] -= blockedCreditsAmount;

delete requestBlockedCredits[requestCount_];
emit CreditsUnblocked(requestCount_, consumeFrom);
Expand Down
14 changes: 7 additions & 7 deletions contracts/evmx/helpers/ForwarderSolana.sol
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,11 @@ 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, bytes32 target) external {
function callSolana(
bytes memory solanaPayload,
bytes32 target,
address callerAppGateway
) external {
if (address(addressResolver__) == address(0)) {
revert AddressResolverNotSet();
}
Expand All @@ -66,18 +69,15 @@ contract ForwarderSolana is ForwarderStorage, Initializable, AddressResolverUtil
}

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

// fetch the override params from app gateway
(OverrideParams memory overrideParams, 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;
Expand Down
Loading