Skip to content

Commit 09b9be5

Browse files
authored
sendTokenWithPayloadToEvmWithRefund (#9)
* sendTokenWithPayloadToEvmWithRefund * naming change * Tests + bugfix
1 parent 6f547ed commit 09b9be5

File tree

3 files changed

+171
-0
lines changed

3 files changed

+171
-0
lines changed

src/WormholeRelayerSDK.sol

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,26 @@ abstract contract TokenSender is TokenBase {
146146
);
147147
}
148148

149+
function sendTokenWithPayloadToEvm(
150+
uint16 targetChain,
151+
address targetAddress,
152+
bytes memory payload,
153+
uint256 receiverValue,
154+
uint256 gasLimit,
155+
address token,
156+
uint256 amount,
157+
uint16 refundChain,
158+
address refundAddress
159+
) internal returns (uint64) {
160+
VaaKey[] memory vaaKeys = new VaaKey[](1);
161+
vaaKeys[0] = transferTokens(token, amount, targetChain, targetAddress);
162+
163+
(uint256 cost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, receiverValue, gasLimit);
164+
return wormholeRelayer.sendVaasToEvm{value: cost}(
165+
targetChain, targetAddress, payload, receiverValue, gasLimit, vaaKeys, refundChain, refundAddress
166+
);
167+
}
168+
149169
function forwardTokenWithPayloadToEvm(
150170
uint16 targetChain,
151171
address targetAddress,

src/testing/WormholeRelayerTest.sol

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ abstract contract WormholeRelayerTest is Test {
131131

132132
vm.selectFork(firstFork.fork);
133133
setUpGeneral();
134+
135+
vm.selectFork(firstFork.fork);
134136
}
135137

136138
function performDelivery() public {
@@ -161,6 +163,9 @@ abstract contract WormholeRelayerTest is Test {
161163
bytes memory attestation = home.guardian.fetchSignedMessageFromLogs(log, home.chainId);
162164

163165
for (uint256 i = 0; i < activeForksList.length; ++i) {
166+
if(activeForksList[i] == home.chainId) {
167+
continue;
168+
}
164169
ActiveFork memory fork = activeForks[activeForksList[i]];
165170
vm.selectFork(fork.fork);
166171
fork.tokenBridge.createWrapped(attestation);

test/Fork.t.sol

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,21 +35,118 @@ contract Toy is Base {
3535
}
3636
}
3737

38+
contract TokenToy is TokenSender, TokenReceiver {
39+
constructor (address _wormholeRelayer, address _bridge, address _wormhole) TokenBase(_wormholeRelayer, _bridge, _wormhole) {}
40+
41+
uint256 constant GAS_LIMIT = 250_000;
42+
43+
function quoteCrossChainDeposit(uint16 targetChain) public view returns (uint256 cost) {
44+
// Cost of delivering token and payload to targetChain
45+
uint256 deliveryCost;
46+
(deliveryCost,) = wormholeRelayer.quoteEVMDeliveryPrice(targetChain, 0, GAS_LIMIT);
47+
48+
// Total cost: delivery cost + cost of publishing the 'sending token' wormhole message
49+
cost = deliveryCost + wormhole.messageFee();
50+
}
51+
52+
function sendCrossChainDeposit(
53+
uint16 targetChain,
54+
address recipient,
55+
uint256 amount,
56+
address token
57+
) public payable {
58+
uint256 cost = quoteCrossChainDeposit(targetChain);
59+
require(msg.value == cost, "msg.value must be quoteCrossChainDeposit(targetChain)");
60+
61+
IERC20(token).transferFrom(msg.sender, address(this), amount);
62+
63+
bytes memory payload = abi.encode(recipient);
64+
sendTokenWithPayloadToEvm(
65+
targetChain,
66+
fromWormholeFormat(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to
67+
payload,
68+
0, // receiver value
69+
GAS_LIMIT,
70+
token, // address of IERC20 token contract
71+
amount
72+
);
73+
}
74+
75+
function sendCrossChainDeposit(
76+
uint16 targetChain,
77+
address recipient,
78+
uint256 amount,
79+
address token,
80+
uint16 refundChain,
81+
address refundAddress
82+
) public payable {
83+
uint256 cost = quoteCrossChainDeposit(targetChain);
84+
require(msg.value == cost, "msg.value must be quoteCrossChainDeposit(targetChain)");
85+
86+
IERC20(token).transferFrom(msg.sender, address(this), amount);
87+
88+
bytes memory payload = abi.encode(recipient);
89+
sendTokenWithPayloadToEvm(
90+
targetChain,
91+
fromWormholeFormat(registeredSenders[targetChain]), // address (on targetChain) to send token and payload to
92+
payload,
93+
0, // receiver value
94+
GAS_LIMIT,
95+
token, // address of IERC20 token contract
96+
amount,
97+
refundChain,
98+
refundAddress
99+
);
100+
}
101+
102+
function receivePayloadAndTokens(
103+
bytes memory payload,
104+
TokenReceived[] memory receivedTokens,
105+
bytes32 sourceAddress,
106+
uint16 sourceChain,
107+
bytes32 // deliveryHash
108+
) internal override onlyWormholeRelayer isRegisteredSender(sourceChain, sourceAddress) {
109+
require(receivedTokens.length == 1, "Expected 1 token transfers");
110+
address recipient = abi.decode(payload, (address));
111+
IERC20(receivedTokens[0].tokenAddress).transfer(recipient, receivedTokens[0].amount);
112+
}
113+
}
114+
38115
contract WormholeSDKTest is WormholeRelayerBasicTest {
39116
Toy toySource;
40117
Toy toyTarget;
118+
TokenToy tokenToySource;
119+
TokenToy tokenToyTarget;
120+
ERC20Mock public token;
41121

42122
function setUpSource() public override {
43123
toySource = new Toy(address(relayerSource), address(wormholeSource));
44124
toySource.setRegisteredSender(targetChain, toWormholeFormat(address(this)));
125+
126+
tokenToySource = new TokenToy(address(relayerSource), address(tokenBridgeSource), address(wormholeSource));
127+
128+
token = createAndAttestToken(sourceChain);
45129
}
46130

47131
function setUpTarget() public override {
48132
toyTarget = new Toy(address(relayerTarget), address(wormholeTarget));
49133
toyTarget.setRegisteredSender(sourceChain, toWormholeFormat(address(this)));
134+
135+
tokenToyTarget = new TokenToy(address(relayerTarget), address(tokenBridgeTarget), address(wormholeTarget));
136+
}
137+
138+
function setUpGeneral() public override {
139+
vm.selectFork(sourceFork);
140+
tokenToySource.setRegisteredSender(targetChain, toWormholeFormat(address(tokenToyTarget)));
141+
142+
143+
vm.selectFork(targetFork);
144+
tokenToyTarget.setRegisteredSender(sourceChain, toWormholeFormat(address(tokenToySource)));
145+
50146
}
51147

52148
function testSendMessage() public {
149+
53150
vm.recordLogs();
54151
(uint256 cost,) = relayerSource.quoteEVMDeliveryPrice(targetChain, 1e17, 100_000);
55152
relayerSource.sendPayloadToEvm{value: cost}(targetChain, address(toyTarget), abi.encode(55), 1e17, 100_000);
@@ -70,4 +167,53 @@ contract WormholeSDKTest is WormholeRelayerBasicTest {
70167
vm.selectFork(sourceFork);
71168
require(56 == toySource.payloadReceived());
72169
}
170+
171+
function testSendToken() public {
172+
173+
vm.selectFork(sourceFork);
174+
175+
uint256 amount = 19e17;
176+
token.approve(address(tokenToySource), amount);
177+
178+
vm.selectFork(targetFork);
179+
address recipient = 0x1234567890123456789012345678901234567890;
180+
181+
vm.selectFork(sourceFork);
182+
uint256 cost = tokenToySource.quoteCrossChainDeposit(targetChain);
183+
184+
vm.recordLogs();
185+
tokenToySource.sendCrossChainDeposit{value: cost}(
186+
targetChain, recipient, amount, address(token)
187+
);
188+
performDelivery();
189+
190+
vm.selectFork(targetFork);
191+
address wormholeWrappedToken = tokenBridgeTarget.wrappedAsset(sourceChain, toWormholeFormat(address(token)));
192+
assertEq(IERC20(wormholeWrappedToken).balanceOf(recipient), amount);
193+
}
194+
195+
function testSendTokenWithRefund() public {
196+
197+
vm.selectFork(sourceFork);
198+
199+
uint256 amount = 19e17;
200+
token.approve(address(tokenToySource), amount);
201+
202+
vm.selectFork(targetFork);
203+
address recipient = 0x1234567890123456789012345678901234567890;
204+
address refundAddress = 0x2234567890123456789012345678901234567890;
205+
vm.selectFork(sourceFork);
206+
uint256 cost = tokenToySource.quoteCrossChainDeposit(targetChain);
207+
208+
vm.recordLogs();
209+
tokenToySource.sendCrossChainDeposit{value: cost}(
210+
targetChain, recipient, amount, address(token), targetChain, refundAddress
211+
);
212+
performDelivery();
213+
214+
vm.selectFork(targetFork);
215+
address wormholeWrappedToken = tokenBridgeTarget.wrappedAsset(sourceChain, toWormholeFormat(address(token)));
216+
assertEq(IERC20(wormholeWrappedToken).balanceOf(recipient), amount);
217+
assertTrue(refundAddress.balance > 0);
218+
}
73219
}

0 commit comments

Comments
 (0)