-
Notifications
You must be signed in to change notification settings - Fork 8
/
WormholeAdapter.sol
203 lines (186 loc) · 7.37 KB
/
WormholeAdapter.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.8.0;
pragma abicoder v2;
import "openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol";
import "wormhole-solidity-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-solidity-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-solidity-sdk/Utils.sol";
import "./interfaces/IWormholeAdapter.sol";
import "@xcall/utils/Types.sol";
import "@xcall/contracts/xcall/interfaces/IConnection.sol";
import "@iconfoundation/xcall-solidity-library/interfaces/ICallService.sol";
/**
* @title WormholeAdapter
* @dev This contract serves as a cross-chain xcall adapter, enabling communication between xcall on different blockchain networks via Wormhole.
*/
contract WormholeAdapter is IWormholeAdapter, Initializable, IWormholeReceiver, IConnection {
mapping(uint256 => Types.PendingResponse) private pendingResponses;
mapping(string => uint16) private chainIds;
mapping(uint16 => string) private networkIds;
mapping(string => uint256) private gasLimits;
mapping(string => uint256) private responseFees;
mapping(string => bytes32) private remoteEndpoint;
mapping(bytes32 => bool) public seenDeliveryVaaHashes;
address private wormholeRelayer;
address private xCall;
address private owner;
address private adminAddress;
modifier onlyOwner() {
require(msg.sender == owner, "OnlyOwner");
_;
}
modifier onlyAdmin() {
require(msg.sender == this.admin(), "OnlyAdmin");
_;
}
function initialize(address _wormholeRelayer, address _xCall) public initializer {
owner = msg.sender;
adminAddress = msg.sender;
wormholeRelayer = _wormholeRelayer;
xCall = _xCall;
}
/**
* @notice Configure connection settings for a destination chain.
* @param networkId The network ID of the destination chain.
* @param chainId The chain ID of the destination chain.
* @param endpoint The endpoint or address of the destination chain.
* @param gasLimit The gas limit for transactions on the destination chain.
* @param responseFee The fee required for a response from the destination chain, to be airdropped to the specified `endpoint`.
*/
function configureConnection(
string calldata networkId,
uint16 chainId,
bytes32 endpoint,
uint256 gasLimit,
uint256 responseFee
) external override onlyAdmin {
require(bytes(networkIds[chainId]).length == 0, "Connection already configured");
networkIds[chainId] = networkId;
chainIds[networkId] = chainId;
remoteEndpoint[networkId] = endpoint;
gasLimits[networkId] = gasLimit;
responseFees[networkId] = responseFee;
}
/**
* @notice set or update gas limit for a destination chain.
* @param networkId The network ID of the destination chain.
* @param gasLimit The gas limit for transactions on the destination chain.
*/
function setGasLimit(
string calldata networkId,
uint256 gasLimit
) external override onlyAdmin {
gasLimits[networkId] = gasLimit;
}
/**
* @notice set or update response fee to a source chain.
* @param networkId The network ID of the destination chain.
* @param responseFee The fee required for a response from the destination chain, to be airdropped to the specified `endpoint`.
*/
function setResponseFee(
string calldata networkId,
uint256 responseFee
) external override onlyAdmin {
responseFees[networkId] = responseFee;
}
/**
* @notice Get the gas fee required to send a message to a specified destination network.
* @param _to The network ID of the target chain.
* @param _response Indicates whether the response fee is included (true) or not (false).
* @return _fee The fee for sending a message to the given destination network.
*/
function getFee(string memory _to, bool _response) external view override returns (uint256 _fee) {
uint256 gasLimit = gasLimits[_to];
uint256 responseFee = 0;
if (_response) {
responseFee = responseFees[_to];
}
(_fee,) = IWormholeRelayer(wormholeRelayer).quoteEVMDeliveryPrice(chainIds[_to], responseFee, gasLimit);
}
/**
* @notice Send a message to a specified destination network.
* @param _to The network ID of the destination network.
* @param _svc The name of the service.
* @param _sn The serial number of the message.
* @param _msg The serialized bytes of the service message.
*/
function sendMessage(
string memory _to,
string memory _svc,
int256 _sn,
bytes calldata _msg
) external override payable {
require(msg.sender == xCall, "Only xCall can send messages");
uint256 fee = msg.value;
if (_sn < 0) {
fee = this.getFee(_to, false);
if (address(this).balance < fee) {
uint256 sn = uint256(- _sn);
pendingResponses[sn] = Types.PendingResponse(_msg, _to);
emit ResponseOnHold(sn);
return;
}
}
IWormholeRelayer(wormholeRelayer).sendPayloadToEvm{value: fee}(
chainIds[_to],
fromWormholeFormat(remoteEndpoint[_to]),
abi.encodePacked(_msg),
_sn > 0 ? responseFees[_to] : 0,
gasLimits[_to]
);
}
/**
* @notice Endpoint that the Wormhole Relayer contract calls to deliver the payload.
*/
function receiveWormholeMessages(
bytes memory payload,
bytes[] memory, // additionalVaas
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) public payable override {
require(msg.sender == wormholeRelayer, "Only relayer allowed");
require(!seenDeliveryVaaHashes[deliveryHash], "Message already processed");
seenDeliveryVaaHashes[deliveryHash] = true;
string memory nid = networkIds[sourceChain];
require(keccak256(abi.encodePacked(sourceAddress)) == keccak256(abi.encodePacked(remoteEndpoint[nid])), "source address mismatched");
ICallService(xCall).handleMessage(nid, payload);
}
/**
* @notice Pay and trigger the execution of a stored response to be sent back.
* @param _sn The serial number of the message for which the response is being triggered.
*/
function triggerResponse(uint256 _sn) external override payable {
int256 sn = int256(_sn);
Types.PendingResponse memory resp = pendingResponses[_sn];
delete pendingResponses[_sn];
uint256 fee = msg.value;
IWormholeRelayer(wormholeRelayer).sendPayloadToEvm{value: fee}(
chainIds[resp.targetNetwork],
fromWormholeFormat(remoteEndpoint[resp.targetNetwork]),
abi.encodePacked(resp.msg),
0,
gasLimits[resp.targetNetwork]
);
}
/**
* @notice Set the address of the admin.
* @param _address The address of the admin.
*/
function setAdmin(address _address) external onlyAdmin {
adminAddress = _address;
}
/**
* @notice Gets the address of admin
* @return (Address) the address of admin
*/
function admin(
) external view returns (
address
) {
if (adminAddress == address(0)) {
return owner;
}
return adminAddress;
}
}