Skip to content

Commit 98b6687

Browse files
s2imonoviclumtisCryptoFewkafadeev
authored
chore: release v15 (#608)
Co-authored-by: Lucas Bertrand <lucas.bertrand.22@gmail.com> Co-authored-by: Christopher Fuka <97121270+CryptoFewka@users.noreply.github.com> Co-authored-by: Denis Fadeev <denis@fadeev.org>
1 parent dbd47a3 commit 98b6687

File tree

152 files changed

+5414
-406
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

152 files changed

+5414
-406
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,4 @@ This guide covers all steps required to deploy the contracts, including environm
7070

7171
## Community
7272

73-
[X](https://x.com/zetablockchain) | [Discord](https://discord.com/invite/zetachain) | [Telegram](https://t.me/zetachainofficial) | [Website](https://zetachain.com)
73+
[X](https://x.com/zetachain) | [Discord](https://discord.com/invite/zetachain) | [Telegram](https://t.me/zetachainofficial) | [Website](https://zetachain.com)

contracts/Revert.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ struct AbortContext {
5454
interface Revertable {
5555
/// @notice Called when a revertable call is made.
5656
/// @param revertContext Revert context to pass to onRevert.
57-
function onRevert(RevertContext calldata revertContext) external payable;
57+
function onRevert(RevertContext calldata revertContext) external;
5858
}
5959

6060
/// @title Abortable

contracts/evm/GatewayEVM.sol

Lines changed: 176 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ contract GatewayEVM is
3838
/// @notice The address of the Zeta token contract.
3939
address public zetaToken;
4040

41+
/// @notice Fee charged for additional cross-chain actions within the same transaction.
42+
/// @dev The first action in a transaction is free, subsequent actions incur this fee.
43+
/// @dev This is configurable by the admin role to allow for fee adjustments.
44+
uint256 public additionalActionFeeWei;
45+
4146
/// @notice New role identifier for tss role.
4247
bytes32 public constant TSS_ROLE = keccak256("TSS_ROLE");
4348
/// @notice New role identifier for asset handler role.
@@ -47,6 +52,11 @@ contract GatewayEVM is
4752
/// @notice Max size of payload + revertOptions revert message.
4853
uint256 public constant MAX_PAYLOAD_SIZE = 2880;
4954

55+
/// @notice Storage slot key for tracking transaction action count.
56+
/// @dev Uses transient storage (tload/tstore) for gas efficiency.
57+
/// @dev Value 0x01 is used as a unique identifier for this storage slot.
58+
uint256 private constant _TRANSACTION_ACTION_COUNT_KEY = 0x01;
59+
5060
/// @custom:oz-upgrades-unsafe-allow constructor
5161
constructor() {
5262
_disableInitializers();
@@ -99,6 +109,17 @@ contract GatewayEVM is
99109
_unpause();
100110
}
101111

112+
/// @notice Update the additional action fee.
113+
/// @dev Only callable by admin role. This allows for fee adjustments based on network conditions.
114+
/// @dev Setting fee to 0 disables additional action fees entirely.
115+
/// @param newFeeWei The new fee amount in wei for additional actions in the same transaction.
116+
/// @dev Fee should be adjusted based on the chain's native token decimals.
117+
function updateAdditionalActionFee(uint256 newFeeWei) external onlyRole(DEFAULT_ADMIN_ROLE) {
118+
uint256 oldFee = additionalActionFeeWei;
119+
additionalActionFeeWei = newFeeWei;
120+
emit UpdatedAdditionalActionFee(oldFee, newFeeWei);
121+
}
122+
102123
/// @notice Transfers msg.value to destination contract and executes it's onRevert function.
103124
/// @dev This function can only be called by the TSS address and it is payable.
104125
/// @param destination Address to call.
@@ -234,18 +255,55 @@ contract GatewayEVM is
234255
/// @notice Deposits ETH to the TSS address.
235256
/// @param receiver Address of the receiver.
236257
/// @param revertOptions Revert options.
258+
/// @dev This function only works for the first action in a transaction (backward compatibility).
259+
/// @dev For subsequent actions, use the overloaded version with amount parameter.
237260
function deposit(address receiver, RevertOptions calldata revertOptions) external payable whenNotPaused {
238261
if (msg.value == 0) revert InsufficientETHAmount();
239262
if (receiver == address(0)) revert ZeroAddress();
240263
if (revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
241264

265+
// Check if this is a subsequent action (action index > 0)
266+
uint256 currentIndex = _getNextActionIndex();
267+
if (currentIndex > 0) {
268+
revert AdditionalActionDisabled();
269+
}
270+
271+
// Legacy behavior: transfer entire msg.value to TSS (no fee processing)
242272
(bool deposited,) = tssAddress.call{ value: msg.value }("");
243273

244274
if (!deposited) revert DepositFailed();
245275

246276
emit Deposited(msg.sender, receiver, msg.value, address(0), "", revertOptions);
247277
}
248278

279+
/// @notice Deposits ETH to the TSS address with specified amount.
280+
/// @param receiver Address of the receiver.
281+
/// @param amount Amount of ETH to deposit (excluding fees).
282+
/// @param revertOptions Revert options.
283+
/// @dev msg.value must equal amount + required fee for the action.
284+
function deposit(
285+
address receiver,
286+
uint256 amount,
287+
RevertOptions calldata revertOptions
288+
)
289+
external
290+
payable
291+
whenNotPaused
292+
{
293+
if (amount == 0) revert InsufficientETHAmount();
294+
if (receiver == address(0)) revert ZeroAddress();
295+
if (revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
296+
297+
uint256 feeCharged = _processFee();
298+
_validateChargedFeeForETHWithAmount(amount, feeCharged);
299+
300+
(bool deposited,) = tssAddress.call{ value: amount }("");
301+
302+
if (!deposited) revert DepositFailed();
303+
304+
emit Deposited(msg.sender, receiver, amount, address(0), "", revertOptions);
305+
}
306+
249307
/// @notice Deposits ERC20 tokens to the custody or connector contract.
250308
/// @param receiver Address of the receiver.
251309
/// @param amount Amount of tokens to deposit.
@@ -258,12 +316,16 @@ contract GatewayEVM is
258316
RevertOptions calldata revertOptions
259317
)
260318
external
319+
payable
261320
whenNotPaused
262321
{
263322
if (amount == 0) revert InsufficientERC20Amount();
264323
if (receiver == address(0)) revert ZeroAddress();
265324
if (revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
266325

326+
uint256 feeCharged = _processFee();
327+
_validateChargedFeeForERC20(feeCharged);
328+
267329
_transferFromToAssetHandler(msg.sender, asset, amount);
268330

269331
emit Deposited(msg.sender, receiver, amount, asset, "", revertOptions);
@@ -273,6 +335,8 @@ contract GatewayEVM is
273335
/// @param receiver Address of the receiver.
274336
/// @param payload Calldata to pass to the call.
275337
/// @param revertOptions Revert options.
338+
/// @dev This function only works for the first action in a transaction (backward compatibility).
339+
/// @dev For subsequent actions, use the overloaded version with amount parameter.
276340
function depositAndCall(
277341
address receiver,
278342
bytes calldata payload,
@@ -286,13 +350,50 @@ contract GatewayEVM is
286350
if (receiver == address(0)) revert ZeroAddress();
287351
if (payload.length + revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
288352

353+
// Check if this is a subsequent action (action index > 0)
354+
uint256 currentIndex = _getNextActionIndex();
355+
if (currentIndex > 0) {
356+
revert AdditionalActionDisabled();
357+
}
358+
359+
// Legacy behavior: transfer entire msg.value to TSS (no fee processing)
289360
(bool deposited,) = tssAddress.call{ value: msg.value }("");
290361

291362
if (!deposited) revert DepositFailed();
292363

293364
emit DepositedAndCalled(msg.sender, receiver, msg.value, address(0), payload, revertOptions);
294365
}
295366

367+
/// @notice Deposits ETH to the TSS address and calls an omnichain smart contract with specified amount.
368+
/// @param receiver Address of the receiver.
369+
/// @param amount Amount of ETH to deposit (excluding fees).
370+
/// @param payload Calldata to pass to the call.
371+
/// @param revertOptions Revert options.
372+
/// @dev msg.value must equal amount + required fee for the action.
373+
function depositAndCall(
374+
address receiver,
375+
uint256 amount,
376+
bytes calldata payload,
377+
RevertOptions calldata revertOptions
378+
)
379+
external
380+
payable
381+
whenNotPaused
382+
{
383+
if (amount == 0) revert InsufficientETHAmount();
384+
if (receiver == address(0)) revert ZeroAddress();
385+
if (payload.length + revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
386+
387+
uint256 feeCharged = _processFee();
388+
_validateChargedFeeForETHWithAmount(amount, feeCharged);
389+
390+
(bool deposited,) = tssAddress.call{ value: amount }("");
391+
392+
if (!deposited) revert DepositFailed();
393+
394+
emit DepositedAndCalled(msg.sender, receiver, amount, address(0), payload, revertOptions);
395+
}
396+
296397
/// @notice Deposits ERC20 tokens to the custody or connector contract and calls an omnichain smart contract.
297398
/// @param receiver Address of the receiver.
298399
/// @param amount Amount of tokens to deposit.
@@ -307,12 +408,16 @@ contract GatewayEVM is
307408
RevertOptions calldata revertOptions
308409
)
309410
external
411+
payable
310412
whenNotPaused
311413
{
312414
if (amount == 0) revert InsufficientERC20Amount();
313415
if (receiver == address(0)) revert ZeroAddress();
314416
if (payload.length + revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
315417

418+
uint256 feeCharged = _processFee();
419+
_validateChargedFeeForERC20(feeCharged);
420+
316421
_transferFromToAssetHandler(msg.sender, asset, amount);
317422

318423
emit DepositedAndCalled(msg.sender, receiver, amount, asset, payload, revertOptions);
@@ -328,12 +433,16 @@ contract GatewayEVM is
328433
RevertOptions calldata revertOptions
329434
)
330435
external
436+
payable
331437
whenNotPaused
332438
{
333439
if (revertOptions.callOnRevert) revert CallOnRevertNotSupported();
334440
if (receiver == address(0)) revert ZeroAddress();
335441
if (payload.length + revertOptions.revertMessage.length > MAX_PAYLOAD_SIZE) revert PayloadSizeExceeded();
336442

443+
uint256 feeCharged = _processFee();
444+
_validateChargedFeeForERC20(feeCharged);
445+
337446
emit Called(msg.sender, receiver, payload, revertOptions);
338447
}
339448

@@ -390,7 +499,7 @@ contract GatewayEVM is
390499
function _transferFromToAssetHandler(address from, address token, uint256 amount) private {
391500
if (token == zetaToken) {
392501
// TODO: remove error and comment out code once ZETA supported back
393-
// https://github.com/zeta-chain/protocol-contracts/issues/394
502+
// https://github.com/zeta-chain/protocol-contracts-evm/issues/394
394503
// ZETA token is currently not supported for deposit
395504
revert ZETANotSupported();
396505

@@ -472,4 +581,70 @@ contract GatewayEVM is
472581
}
473582
}
474583
}
584+
585+
/// @notice Processes fee collection for cross-chain actions within a transaction.
586+
/// @dev The first action in a transaction is free, subsequent actions incur ADDITIONAL_ACTION_FEE_WEI.
587+
/// @dev If fee is 0, the entire functionality is disabled and will revert.
588+
/// @return The fee amount actually charged (0 for first action, ADDITIONAL_ACTION_FEE_WEI for
589+
/// subsequent actions).
590+
function _processFee() internal returns (uint256) {
591+
uint256 actionIndex = _getNextActionIndex();
592+
593+
// First action is free
594+
if (actionIndex == 0) {
595+
return 0;
596+
}
597+
598+
// If fee is 0, functionality is disabled
599+
if (additionalActionFeeWei == 0) {
600+
revert AdditionalActionDisabled();
601+
}
602+
603+
// Subsequent actions require fee payment
604+
if (msg.value < additionalActionFeeWei) {
605+
revert InsufficientFee(additionalActionFeeWei, msg.value);
606+
}
607+
608+
// Transfer fee to TSS address
609+
(bool success,) = tssAddress.call{ value: additionalActionFeeWei }("");
610+
if (!success) {
611+
revert FeeTransferFailed();
612+
}
613+
614+
return additionalActionFeeWei;
615+
}
616+
617+
/// @notice Validates fee payment for ERC20 operations (deposit, depositAndCall, call).
618+
/// @dev Validates that msg.value equals the required fee (no excess ETH allowed).
619+
/// @param feeCharged The fee amount that was charged.
620+
function _validateChargedFeeForERC20(uint256 feeCharged) internal view {
621+
// For ERC20 operations, msg.value must equal the required fee
622+
if (msg.value > feeCharged) {
623+
revert ExcessETHProvided(feeCharged, msg.value);
624+
}
625+
}
626+
627+
/// @notice Validates fee payment for ETH operations with specified amount.
628+
/// @dev Validates that msg.value equals amount + feeCharged.
629+
/// @param amount The amount to deposit (excluding fees).
630+
/// @param feeCharged The fee amount that was charged.
631+
function _validateChargedFeeForETHWithAmount(uint256 amount, uint256 feeCharged) internal view {
632+
uint256 expectedValue = amount + feeCharged;
633+
if (msg.value != expectedValue) {
634+
revert IncorrectValueProvided(expectedValue, msg.value);
635+
}
636+
}
637+
638+
/// @notice Gets and increments the transaction action counter using transient storage.
639+
/// @dev Uses assembly for gas efficiency with tload/tstore operations.
640+
/// @dev Transient storage is transaction-scoped and automatically cleared after each transaction.
641+
/// @return currentIndex The current action index within the transaction (0-based).
642+
function _getNextActionIndex() internal returns (uint256 currentIndex) {
643+
assembly {
644+
// Load current count from transient storage
645+
currentIndex := tload(_TRANSACTION_ACTION_COUNT_KEY)
646+
// Increment and store back to transient storage
647+
tstore(_TRANSACTION_ACTION_COUNT_KEY, add(currentIndex, 1))
648+
}
649+
}
475650
}

contracts/evm/ZetaConnectorNative.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ contract ZetaConnectorNative is ZetaConnectorBase {
2828
/// @param to The address to withdraw tokens to.
2929
/// @param amount The amount of tokens to withdraw.
3030
//// @param internalSendHash A hash used for internal tracking of the transaction (not used currently
31-
// https://github.com/zeta-chain/protocol-contracts/issues/398)
31+
// https://github.com/zeta-chain/protocol-contracts-evm/issues/398)
3232
/// @dev This function can only be called by the TSS address.
3333
function withdraw(
3434
address to,
@@ -51,7 +51,7 @@ contract ZetaConnectorNative is ZetaConnectorBase {
5151
/// @param amount The amount of tokens to withdraw.
5252
/// @param data The calldata to pass to the contract call.
5353
//// @param internalSendHash A hash used for internal tracking of the transaction (not used currently
54-
// https://github.com/zeta-chain/protocol-contracts/issues/398)
54+
// https://github.com/zeta-chain/protocol-contracts-evm/issues/398)
5555
/// @dev This function can only be called by the TSS address.
5656
function withdrawAndCall(
5757
MessageContext calldata messageContext,
@@ -80,7 +80,7 @@ contract ZetaConnectorNative is ZetaConnectorBase {
8080
/// @param amount The amount of tokens to withdraw.
8181
/// @param data The calldata to pass to the contract call.
8282
//// @param internalSendHash A hash used for internal tracking of the transaction (not used currently
83-
// https://github.com/zeta-chain/protocol-contracts/issues/398)
83+
// https://github.com/zeta-chain/protocol-contracts-evm/issues/398)
8484
/// @dev This function can only be called by the TSS address.
8585
/// @param revertContext Revert context to pass to onRevert.
8686
function withdrawAndRevert(

0 commit comments

Comments
 (0)