-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathTokenBase.sol
233 lines (212 loc) · 7.59 KB
/
TokenBase.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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
// SPDX-License-Identifier: Apache 2
pragma solidity ^0.8.19;
import "IERC20/IERC20.sol";
import "wormhole-sdk/interfaces/IWormholeReceiver.sol";
import "wormhole-sdk/interfaces/IWormholeRelayer.sol";
import "wormhole-sdk/interfaces/ITokenBridge.sol";
import "wormhole-sdk/Utils.sol";
import {Base} from "./Base.sol";
abstract contract TokenBase is Base {
ITokenBridge public immutable tokenBridge;
constructor(
address _wormholeRelayer,
address _tokenBridge,
address _wormhole
) Base(_wormholeRelayer, _wormhole) {
tokenBridge = ITokenBridge(_tokenBridge);
}
}
abstract contract TokenSender is TokenBase {
/**
* transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer
* - approves tokenBridge to spend 'amount' of 'token'
* - emits token transfer VAA
* - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument
*
* Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress
* can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by
* the offchain relayer and the target contract would have to be hardened against this.
*
*/
function transferTokens(
address token,
uint256 amount,
uint16 targetChain,
address targetAddress
) internal returns (VaaKey memory) {
return transferTokens(
token,
amount,
targetChain,
targetAddress,
bytes("")
);
}
/**
* transferTokens wraps common boilerplate for sending tokens to another chain using IWormholeRelayer.
* A payload can be included in the transfer vaa. By including a payload here instead of the deliveryVaa,
* fewer trust assumptions are placed on the WormholeRelayer contract.
*
* - approves tokenBridge to spend 'amount' of 'token'
* - emits token transfer VAA
* - returns VAA key for inclusion in WormholeRelayer `additionalVaas` argument
*
* Note: this function uses transferTokensWithPayload instead of transferTokens since the former requires that only the targetAddress
* can redeem transfers. Otherwise it's possible for another address to redeem the transfer before the targetContract is invoked by
* the offchain relayer and the target contract would have to be hardened against this.
*/
function transferTokens(
address token,
uint256 amount,
uint16 targetChain,
address targetAddress,
bytes memory payload
) internal returns (VaaKey memory) {
IERC20(token).approve(address(tokenBridge), amount);
uint64 sequence = tokenBridge.transferTokensWithPayload{
value: wormhole.messageFee()
}(
token,
amount,
targetChain,
toUniversalAddress(targetAddress),
0,
payload
);
return VaaKey({
emitterAddress: toUniversalAddress(address(tokenBridge)),
chainId: wormhole.chainId(),
sequence: sequence
});
}
// Publishes a wormhole message representing a 'TokenBridge' transfer of 'amount' of 'token'
// and requests a delivery of the transfer along with 'payload' to 'targetAddress' on 'targetChain'
//
// The second step is done by publishing a wormhole message representing a request
// to call 'receiveWormholeMessages' on the address 'targetAddress' on chain 'targetChain'
// with the payload 'payload'
function sendTokenWithPayloadToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 gasLimit,
address token,
uint256 amount
) internal returns (uint64) {
VaaKey[] memory vaaKeys = new VaaKey[](1);
vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress);
(uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit);
return wormholeRelayer.sendVaasToEvm{value: cost}(
targetChain,
targetAddress,
payload,
receiverValue,
gasLimit,
vaaKeys
);
}
function sendTokenWithPayloadToEvm(
uint16 targetChain,
address targetAddress,
bytes memory payload,
uint256 receiverValue,
uint256 gasLimit,
address token,
uint256 amount,
uint16 refundChain,
address refundAddress
) internal returns (uint64) {
VaaKey[] memory vaaKeys = new VaaKey[](1);
vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress);
(uint256 cost, ) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit);
return wormholeRelayer.sendVaasToEvm{value: cost}(
targetChain,
targetAddress,
payload,
receiverValue,
gasLimit,
vaaKeys,
refundChain,
refundAddress
);
}
}
abstract contract TokenReceiver is TokenBase {
struct TokenReceived {
bytes32 tokenHomeAddress;
uint16 tokenHomeChain;
address tokenAddress; // wrapped address if tokenHomeChain !== this chain, else tokenHomeAddress (in evm address format)
uint256 amount;
uint256 amountNormalized; // if decimals > 8, normalized to 8 decimal places
}
function getDecimals(
address tokenAddress
) internal view returns (uint8 decimals) {
// query decimals
(, bytes memory queriedDecimals) = address(tokenAddress).staticcall(
abi.encodeWithSignature("decimals()")
);
decimals = abi.decode(queriedDecimals, (uint8));
}
function getTokenAddressOnThisChain(
uint16 tokenHomeChain,
bytes32 tokenHomeAddress
) internal view returns (address tokenAddressOnThisChain) {
return tokenHomeChain == wormhole.chainId()
? fromUniversalAddress(tokenHomeAddress)
: tokenBridge.wrappedAsset(tokenHomeChain, tokenHomeAddress);
}
function receiveWormholeMessages(
bytes memory payload,
bytes[] memory additionalVaas,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) external payable {
TokenReceived[] memory receivedTokens = new TokenReceived[](additionalVaas.length);
for (uint256 i = 0; i < additionalVaas.length; ++i) {
IWormhole.VM memory parsed = wormhole.parseVM(additionalVaas[i]);
require(
parsed.emitterAddress == tokenBridge.bridgeContracts(parsed.emitterChainId),
"Not a Token Bridge VAA"
);
ITokenBridge.TransferWithPayload memory transfer =
tokenBridge.parseTransferWithPayload(parsed.payload);
require(
transfer.to == toUniversalAddress(address(this)) && transfer.toChain == wormhole.chainId(),
"Token was not sent to this address"
);
tokenBridge.completeTransferWithPayload(additionalVaas[i]);
address thisChainTokenAddress =
getTokenAddressOnThisChain(transfer.tokenChain, transfer.tokenAddress);
uint8 decimals = getDecimals(thisChainTokenAddress);
uint256 denormalizedAmount = transfer.amount;
if (decimals > 8)
denormalizedAmount *= uint256(10) ** (decimals - 8);
receivedTokens[i] = TokenReceived({
tokenHomeAddress: transfer.tokenAddress,
tokenHomeChain: transfer.tokenChain,
tokenAddress: thisChainTokenAddress,
amount: denormalizedAmount,
amountNormalized: transfer.amount
});
}
// call into overriden method
receivePayloadAndTokens(
payload,
receivedTokens,
sourceAddress,
sourceChain,
deliveryHash
);
}
// Implement this function to handle in-bound deliveries that include a TokenBridge transfer
function receivePayloadAndTokens(
bytes memory payload,
TokenReceived[] memory receivedTokens,
bytes32 sourceAddress,
uint16 sourceChain,
bytes32 deliveryHash
) internal virtual {}
}