Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add executeAbort to gatewayZEVM #452

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 19 additions & 1 deletion contracts/zevm/GatewayZEVM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity 0.8.26;
import { CallOptions, IGatewayZEVM } from "./interfaces/IGatewayZEVM.sol";

import { INotSupportedMethods } from "../../contracts/Errors.sol";
import { RevertContext, RevertOptions, Revertable } from "../../contracts/Revert.sol";
import { AbortContext, Abortable, RevertContext, RevertOptions, Revertable } from "../../contracts/Revert.sol";
import "./interfaces/IWZETA.sol";
import { IZRC20 } from "./interfaces/IZRC20.sol";
import { MessageContext, UniversalContract } from "./interfaces/UniversalContract.sol";
Expand Down Expand Up @@ -439,4 +439,22 @@ contract GatewayZEVM is
if (!IZRC20(zrc20).deposit(target, amount)) revert ZRC20DepositFailed();
Revertable(target).onRevert(revertContext);
}

/// @notice Call onAbort on a user-specified contract on ZEVM.
/// this function doesn't deposit the asset to the target contract. This operation is done directly by the protocol.
/// the assets are deposited to the target contract even if onAbort reverts.
/// @param target The target contract to call.
/// @param abortContext Abort context to pass to onAbort.
function executeAbort(
address target,
AbortContext calldata abortContext
)
external
nonReentrant
onlyProtocol
whenNotPaused
{
if (target == address(0)) revert ZeroAddress();
Abortable(target).onAbort(abortContext);
}
}
25 changes: 25 additions & 0 deletions docs/src/contracts/zevm/GatewayZEVM.sol/contract.GatewayZEVM.md
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,31 @@ function depositAndRevert(
|`revertContext`|`RevertContext`|Revert context to pass to onRevert.|


### executeAbort

Call onAbort on a user-specified contract on ZEVM.
this function doesn't deposit the asset to the target contract. This operation is done directly by the protocol.
the assets are deposited to the target contract even if onAbort reverts.


```solidity
function executeAbort(
address target,
AbortContext calldata abortContext
)
external
nonReentrant
onlyProtocol
whenNotPaused;
```
**Parameters**

|Name|Type|Description|
|----|----|-----------|
|`target`|`address`|The target contract to call.|
|`abortContext`|`AbortContext`|Abort context to pass to onAbort.|


## Errors
### ZeroAddress
Error indicating a zero address was provided.
Expand Down
2 changes: 1 addition & 1 deletion pkg/gatewayevmzevm.t.sol/gatewayevmzevmtest.go

Large diffs are not rendered by default.

35 changes: 33 additions & 2 deletions pkg/gatewayzevm.sol/gatewayzevm.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/gatewayzevm.t.sol/gatewayzevminboundtest.go

Large diffs are not rendered by default.

190 changes: 188 additions & 2 deletions pkg/gatewayzevm.t.sol/gatewayzevmoutboundtest.go

Large diffs are not rendered by default.

169 changes: 167 additions & 2 deletions pkg/testuniversalcontract.sol/testuniversalcontract.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pkg/zrc20.t.sol/zrc20test.go

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions test/GatewayZEVM.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,11 @@ contract GatewayZEVMOutboundTest is Test, IGatewayZEVMEvents, IGatewayZEVMErrors
address protocolAddress;
RevertOptions revertOptions;
RevertContext revertContext;
AbortContext abortContext;

event ContextData(bytes origin, address sender, uint256 chainID, address msgSender, string message);
event ContextDataRevert(RevertContext revertContext);
event ContextDataAbort(AbortContext abortContext);

error ZeroAddress();
error EnforcedPause();
Expand Down Expand Up @@ -682,6 +684,14 @@ contract GatewayZEVMOutboundTest is Test, IGatewayZEVMEvents, IGatewayZEVMErrors
vm.stopPrank();

revertContext = RevertContext({ sender: owner, asset: address(0), amount: 1, revertMessage: "" });
abortContext = AbortContext({
sender: abi.encodePacked(owner),
asset: address(0),
amount: 1,
outgoing: false,
chainID: 1,
revertMessage: ""
});
}

function testDepositFailsIfZRC20IsZeroAddress() public {
Expand Down Expand Up @@ -1065,4 +1075,17 @@ contract GatewayZEVMOutboundTest is Test, IGatewayZEVMEvents, IGatewayZEVMErrors
vm.prank(protocolAddress);
gateway.depositAndCall(context, amount, address(gateway), message);
}

function testExecuteAbortUniversalContract() public {
vm.expectEmit(true, true, true, true, address(testUniversalContract));
emit ContextDataAbort(abortContext);
vm.prank(protocolAddress);
gateway.executeAbort(address(testUniversalContract), abortContext);
}

function testExecuteAbortUniversalContractFailsIfTargetIsZeroAddress() public {
vm.prank(protocolAddress);
vm.expectRevert(ZeroAddress.selector);
gateway.executeAbort(address(0), abortContext);
}
}
14 changes: 12 additions & 2 deletions test/utils/TestUniversalContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ pragma solidity 0.8.26;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { RevertContext, Revertable } from "../../contracts/Revert.sol";
import { AbortContext, Abortable, RevertContext, Revertable } from "../../contracts/Revert.sol";
import "../../contracts/zevm/interfaces/UniversalContract.sol";

/// @title TestUniversalContract
/// @notice This contract is used just for testing.
/// @dev Implements the UniversalContract interface for handling cross-chain calls and reverts.
contract TestUniversalContract is UniversalContract, Revertable {
contract TestUniversalContract is UniversalContract, Revertable, Abortable {
/// @notice Emitted when a cross-chain call is received.
/// @param origin The origin address on the external chain.
/// @param sender The sender address on the external chain.
Expand All @@ -22,6 +22,10 @@ contract TestUniversalContract is UniversalContract, Revertable {
/// @param revertContext Revert context.
event ContextDataRevert(RevertContext revertContext);

/// @notice Emitted when a cross-chain call is aborted.
/// @param abortContext Abort context.
event ContextDataAbort(AbortContext abortContext);

/// @notice Handles a cross-chain call.
/// @param context The context of the cross-chain call.
//// @param zrc20 The address of the ZRC20 token.
Expand Down Expand Up @@ -50,6 +54,12 @@ contract TestUniversalContract is UniversalContract, Revertable {
emit ContextDataRevert(revertContext);
}

/// @notice Handles a cross-chain call abort.
/// @param abortContext Abort context.
function onAbort(AbortContext calldata abortContext) external override {
emit ContextDataAbort(abortContext);
}

/// @notice Allows the contract to receive ETH.
receive() external payable { }

Expand Down
47 changes: 47 additions & 0 deletions types/GatewayZEVM.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,31 @@ export type RevertContextStructOutput = [
revertMessage: string
] & { sender: string; asset: string; amount: bigint; revertMessage: string };

export type AbortContextStruct = {
sender: BytesLike;
asset: AddressLike;
amount: BigNumberish;
outgoing: boolean;
chainID: BigNumberish;
revertMessage: BytesLike;
};

export type AbortContextStructOutput = [
sender: string,
asset: string,
amount: bigint,
outgoing: boolean,
chainID: bigint,
revertMessage: string
] & {
sender: string;
asset: string;
amount: bigint;
outgoing: boolean;
chainID: bigint;
revertMessage: string;
};

export interface GatewayZEVMInterface extends Interface {
getFunction(
nameOrSignature:
Expand All @@ -95,6 +120,7 @@ export interface GatewayZEVMInterface extends Interface {
| "depositAndCall((bytes,address,uint256),address,uint256,address,bytes)"
| "depositAndRevert"
| "execute"
| "executeAbort"
| "executeRevert"
| "getRoleAdmin"
| "grantRole"
Expand Down Expand Up @@ -191,6 +217,10 @@ export interface GatewayZEVMInterface extends Interface {
BytesLike
]
): string;
encodeFunctionData(
functionFragment: "executeAbort",
values: [AddressLike, AbortContextStruct]
): string;
encodeFunctionData(
functionFragment: "executeRevert",
values: [AddressLike, RevertContextStruct]
Expand Down Expand Up @@ -301,6 +331,10 @@ export interface GatewayZEVMInterface extends Interface {
data: BytesLike
): Result;
decodeFunctionResult(functionFragment: "execute", data: BytesLike): Result;
decodeFunctionResult(
functionFragment: "executeAbort",
data: BytesLike
): Result;
decodeFunctionResult(
functionFragment: "executeRevert",
data: BytesLike
Expand Down Expand Up @@ -691,6 +725,12 @@ export interface GatewayZEVM extends BaseContract {
"nonpayable"
>;

executeAbort: TypedContractMethod<
[target: AddressLike, abortContext: AbortContextStruct],
[void],
"nonpayable"
>;

executeRevert: TypedContractMethod<
[target: AddressLike, revertContext: RevertContextStruct],
[void],
Expand Down Expand Up @@ -888,6 +928,13 @@ export interface GatewayZEVM extends BaseContract {
[void],
"nonpayable"
>;
getFunction(
nameOrSignature: "executeAbort"
): TypedContractMethod<
[target: AddressLike, abortContext: AbortContextStruct],
[void],
"nonpayable"
>;
getFunction(
nameOrSignature: "executeRevert"
): TypedContractMethod<
Expand Down
82 changes: 80 additions & 2 deletions types/TestUniversalContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@ import type {
TypedContractMethod,
} from "./common";

export type AbortContextStruct = {
sender: BytesLike;
asset: AddressLike;
amount: BigNumberish;
outgoing: boolean;
chainID: BigNumberish;
revertMessage: BytesLike;
};

export type AbortContextStructOutput = [
sender: string,
asset: string,
amount: bigint,
outgoing: boolean,
chainID: bigint,
revertMessage: string
] & {
sender: string;
asset: string;
amount: bigint;
outgoing: boolean;
chainID: bigint;
revertMessage: string;
};

export type MessageContextStruct = {
origin: BytesLike;
sender: AddressLike;
Expand Down Expand Up @@ -50,12 +75,21 @@ export type RevertContextStructOutput = [
] & { sender: string; asset: string; amount: bigint; revertMessage: string };

export interface TestUniversalContractInterface extends Interface {
getFunction(nameOrSignature: "onCall" | "onRevert"): FunctionFragment;
getFunction(
nameOrSignature: "onAbort" | "onCall" | "onRevert"
): FunctionFragment;

getEvent(
nameOrSignatureOrTopic: "ContextData" | "ContextDataRevert"
nameOrSignatureOrTopic:
| "ContextData"
| "ContextDataAbort"
| "ContextDataRevert"
): EventFragment;

encodeFunctionData(
functionFragment: "onAbort",
values: [AbortContextStruct]
): string;
encodeFunctionData(
functionFragment: "onCall",
values: [MessageContextStruct, AddressLike, BigNumberish, BytesLike]
Expand All @@ -65,6 +99,7 @@ export interface TestUniversalContractInterface extends Interface {
values: [RevertContextStruct]
): string;

decodeFunctionResult(functionFragment: "onAbort", data: BytesLike): Result;
decodeFunctionResult(functionFragment: "onCall", data: BytesLike): Result;
decodeFunctionResult(functionFragment: "onRevert", data: BytesLike): Result;
}
Expand Down Expand Up @@ -97,6 +132,18 @@ export namespace ContextDataEvent {
export type LogDescription = TypedLogDescription<Event>;
}

export namespace ContextDataAbortEvent {
export type InputTuple = [abortContext: AbortContextStruct];
export type OutputTuple = [abortContext: AbortContextStructOutput];
export interface OutputObject {
abortContext: AbortContextStructOutput;
}
export type Event = TypedContractEvent<InputTuple, OutputTuple, OutputObject>;
export type Filter = TypedDeferredTopicFilter<Event>;
export type Log = TypedEventLog<Event>;
export type LogDescription = TypedLogDescription<Event>;
}

export namespace ContextDataRevertEvent {
export type InputTuple = [revertContext: RevertContextStruct];
export type OutputTuple = [revertContext: RevertContextStructOutput];
Expand Down Expand Up @@ -152,6 +199,12 @@ export interface TestUniversalContract extends BaseContract {
event?: TCEvent
): Promise<this>;

onAbort: TypedContractMethod<
[abortContext: AbortContextStruct],
[void],
"nonpayable"
>;

onCall: TypedContractMethod<
[
context: MessageContextStruct,
Expand All @@ -173,6 +226,13 @@ export interface TestUniversalContract extends BaseContract {
key: string | FunctionFragment
): T;

getFunction(
nameOrSignature: "onAbort"
): TypedContractMethod<
[abortContext: AbortContextStruct],
[void],
"nonpayable"
>;
getFunction(
nameOrSignature: "onCall"
): TypedContractMethod<
Expand Down Expand Up @@ -200,6 +260,13 @@ export interface TestUniversalContract extends BaseContract {
ContextDataEvent.OutputTuple,
ContextDataEvent.OutputObject
>;
getEvent(
key: "ContextDataAbort"
): TypedContractEvent<
ContextDataAbortEvent.InputTuple,
ContextDataAbortEvent.OutputTuple,
ContextDataAbortEvent.OutputObject
>;
getEvent(
key: "ContextDataRevert"
): TypedContractEvent<
Expand All @@ -220,6 +287,17 @@ export interface TestUniversalContract extends BaseContract {
ContextDataEvent.OutputObject
>;

"ContextDataAbort(tuple)": TypedContractEvent<
ContextDataAbortEvent.InputTuple,
ContextDataAbortEvent.OutputTuple,
ContextDataAbortEvent.OutputObject
>;
ContextDataAbort: TypedContractEvent<
ContextDataAbortEvent.InputTuple,
ContextDataAbortEvent.OutputTuple,
ContextDataAbortEvent.OutputObject
>;

"ContextDataRevert(tuple)": TypedContractEvent<
ContextDataRevertEvent.InputTuple,
ContextDataRevertEvent.OutputTuple,
Expand Down
Loading
Loading