-
Notifications
You must be signed in to change notification settings - Fork 327
/
Copy pathTokenPortal.t.sol
269 lines (214 loc) · 9.55 KB
/
TokenPortal.t.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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
pragma solidity >=0.8.18;
import "forge-std/Test.sol";
// Rollup Processor
import {Rollup} from "../../src/core/Rollup.sol";
import {AvailabilityOracle} from "../../src/core/availability_oracle/AvailabilityOracle.sol";
import {Constants} from "../../src/core/libraries/ConstantsGen.sol";
import {Registry} from "../../src/core/messagebridge/Registry.sol";
import {DataStructures} from "../../src/core/libraries/DataStructures.sol";
import {Hash} from "../../src/core/libraries/Hash.sol";
import {Errors} from "../../src/core/libraries/Errors.sol";
// Interfaces
import {IInbox} from "../../src/core/interfaces/messagebridge/IInbox.sol";
import {IOutbox} from "../../src/core/interfaces/messagebridge/IOutbox.sol";
import {IFeeJuicePortal} from "../../src/core/interfaces/IFeeJuicePortal.sol";
// Portal tokens
import {TokenPortal} from "./TokenPortal.sol";
import {PortalERC20} from "./PortalERC20.sol";
import {NaiveMerkle} from "../merkle/Naive.sol";
contract TokenPortalTest is Test {
using Hash for DataStructures.L1ToL2Msg;
event MessageConsumed(bytes32 indexed messageHash, address indexed recipient);
uint256 internal constant FIRST_REAL_TREE_NUM = Constants.INITIAL_L2_BLOCK_NUM + 1;
Registry internal registry;
IInbox internal inbox;
IOutbox internal outbox;
Rollup internal rollup;
bytes32 internal l2TokenAddress = bytes32(uint256(0x42));
TokenPortal internal tokenPortal;
PortalERC20 internal portalERC20;
// input params
uint32 internal deadline = uint32(block.timestamp + 1 days);
bytes32 internal to = bytes32(0x2d749407d8c364537cdeb799c1574929cb22ff1ece2b96d2a1c6fa287a0e0171);
uint256 internal amount = 100;
uint256 internal mintAmount = 1 ether;
// this hash is just a random 32 byte string
bytes32 internal secretHashForL2MessageConsumption =
0x147e4fec49805c924e28150fc4b36824679bc17ecb1d7d9f6a9effb7fde6b6a0;
// this hash is just a random 32 byte string
bytes32 internal secretHashForRedeemingMintedNotes =
0x157e4fec49805c924e28150fc4b36824679bc17ecb1d7d9f6a9effb7fde6b6a0;
// params for withdraw:
address internal recipient = address(0xdead);
uint256 internal withdrawAmount = 654;
uint256 internal l2BlockNumber = 69;
function setUp() public {
registry = new Registry(address(this));
portalERC20 = new PortalERC20();
rollup = new Rollup(
registry, new AvailabilityOracle(), IFeeJuicePortal(address(0)), bytes32(0), address(this)
);
inbox = rollup.INBOX();
outbox = rollup.OUTBOX();
registry.upgrade(address(rollup));
tokenPortal = new TokenPortal();
tokenPortal.initialize(address(registry), address(portalERC20), l2TokenAddress);
// Modify the proven block count
vm.store(address(rollup), bytes32(uint256(7)), bytes32(l2BlockNumber + 1));
assertEq(rollup.provenBlockCount(), l2BlockNumber + 1);
vm.deal(address(this), 100 ether);
}
function _createExpectedMintPrivateL1ToL2Message()
internal
view
returns (DataStructures.L1ToL2Msg memory)
{
return DataStructures.L1ToL2Msg({
sender: DataStructures.L1Actor(address(tokenPortal), block.chainid),
recipient: DataStructures.L2Actor(l2TokenAddress, 1),
content: Hash.sha256ToField(
abi.encodeWithSignature(
"mint_private(bytes32,uint256)", secretHashForRedeemingMintedNotes, amount
)
),
secretHash: secretHashForL2MessageConsumption
});
}
function _createExpectedMintPublicL1ToL2Message()
internal
view
returns (DataStructures.L1ToL2Msg memory)
{
return DataStructures.L1ToL2Msg({
sender: DataStructures.L1Actor(address(tokenPortal), block.chainid),
recipient: DataStructures.L2Actor(l2TokenAddress, 1),
content: Hash.sha256ToField(abi.encodeWithSignature("mint_public(bytes32,uint256)", to, amount)),
secretHash: secretHashForL2MessageConsumption
});
}
function testDepositPrivate() public returns (bytes32) {
// mint token and approve to the portal
portalERC20.mint(address(this), mintAmount);
portalERC20.approve(address(tokenPortal), mintAmount);
// Check for the expected message
DataStructures.L1ToL2Msg memory expectedMessage = _createExpectedMintPrivateL1ToL2Message();
bytes32 expectedLeaf = expectedMessage.sha256ToField();
// Check the event was emitted
vm.expectEmit(true, true, true, true);
// event we expect
emit IInbox.MessageSent(FIRST_REAL_TREE_NUM, 0, expectedLeaf);
// event we will get
// Perform op
bytes32 leaf = tokenPortal.depositToAztecPrivate(
secretHashForRedeemingMintedNotes, amount, secretHashForL2MessageConsumption
);
assertEq(leaf, expectedLeaf, "returned leaf and calculated leaf should match");
return leaf;
}
function testDepositPublic() public returns (bytes32) {
// mint token and approve to the portal
portalERC20.mint(address(this), mintAmount);
portalERC20.approve(address(tokenPortal), mintAmount);
// Check for the expected message
DataStructures.L1ToL2Msg memory expectedMessage = _createExpectedMintPublicL1ToL2Message();
bytes32 expectedLeaf = expectedMessage.sha256ToField();
// Check the event was emitted
vm.expectEmit(true, true, true, true);
// event we expect
emit IInbox.MessageSent(FIRST_REAL_TREE_NUM, 0, expectedLeaf);
// Perform op
bytes32 leaf = tokenPortal.depositToAztecPublic(to, amount, secretHashForL2MessageConsumption);
assertEq(leaf, expectedLeaf, "returned leaf and calculated leaf should match");
return leaf;
}
function _createWithdrawMessageForOutbox(address _designatedCaller)
internal
returns (bytes32, bytes32)
{
bytes32 l2ToL1Message = Hash.sha256ToField(
DataStructures.L2ToL1Msg({
sender: DataStructures.L2Actor({actor: l2TokenAddress, version: 1}),
recipient: DataStructures.L1Actor({actor: address(tokenPortal), chainId: block.chainid}),
content: Hash.sha256ToField(
abi.encodeWithSignature(
"withdraw(address,uint256,address)", recipient, withdrawAmount, _designatedCaller
)
)
})
);
uint256 treeHeight = 1;
NaiveMerkle tree = new NaiveMerkle(treeHeight);
tree.insertLeaf(l2ToL1Message);
bytes32 treeRoot = tree.computeRoot();
return (l2ToL1Message, treeRoot);
}
function _addWithdrawMessageInOutbox(address _designatedCaller, uint256 _l2BlockNumber)
internal
returns (bytes32, bytes32[] memory, bytes32)
{
// send assets to the portal
portalERC20.mint(address(tokenPortal), withdrawAmount);
// Create the message
(bytes32 l2ToL1Message,) = _createWithdrawMessageForOutbox(_designatedCaller);
uint256 treeHeight = 1;
NaiveMerkle tree = new NaiveMerkle(treeHeight);
tree.insertLeaf(l2ToL1Message);
(bytes32[] memory siblingPath,) = tree.computeSiblingPath(0);
bytes32 treeRoot = tree.computeRoot();
// Insert messages into the outbox (impersonating the rollup contract)
vm.prank(address(rollup));
outbox.insert(_l2BlockNumber, treeRoot, treeHeight);
return (l2ToL1Message, siblingPath, treeRoot);
}
function testAnyoneCanCallWithdrawIfNoDesignatedCaller(address _caller) public {
vm.assume(_caller != address(0));
// add message with caller as this address
(bytes32 l2ToL1Message, bytes32[] memory siblingPath, bytes32 treeRoot) =
_addWithdrawMessageInOutbox(address(0), l2BlockNumber);
assertEq(portalERC20.balanceOf(recipient), 0);
vm.startPrank(_caller);
vm.expectEmit(true, true, true, true);
emit IOutbox.MessageConsumed(l2BlockNumber, treeRoot, l2ToL1Message, 0);
tokenPortal.withdraw(recipient, withdrawAmount, false, l2BlockNumber, 0, siblingPath);
// Should have received 654 RNA tokens
assertEq(portalERC20.balanceOf(recipient), withdrawAmount);
// Should not be able to withdraw again
vm.expectRevert(
abi.encodeWithSelector(Errors.Outbox__AlreadyNullified.selector, l2BlockNumber, 0)
);
tokenPortal.withdraw(recipient, withdrawAmount, false, l2BlockNumber, 0, siblingPath);
vm.stopPrank();
}
function testWithdrawWithDesignatedCallerFailsForOtherCallers(address _caller) public {
vm.assume(_caller != address(this));
// add message with caller as this address
(, bytes32[] memory siblingPath, bytes32 treeRoot) =
_addWithdrawMessageInOutbox(address(this), l2BlockNumber);
vm.startPrank(_caller);
(bytes32 l2ToL1MessageHash, bytes32 consumedRoot) = _createWithdrawMessageForOutbox(_caller);
vm.expectRevert(
abi.encodeWithSelector(
Errors.MerkleLib__InvalidRoot.selector, treeRoot, consumedRoot, l2ToL1MessageHash, 0
)
);
tokenPortal.withdraw(recipient, withdrawAmount, true, l2BlockNumber, 0, siblingPath);
(l2ToL1MessageHash, consumedRoot) = _createWithdrawMessageForOutbox(address(0));
vm.expectRevert(
abi.encodeWithSelector(
Errors.MerkleLib__InvalidRoot.selector, treeRoot, consumedRoot, l2ToL1MessageHash, 0
)
);
tokenPortal.withdraw(recipient, withdrawAmount, false, l2BlockNumber, 0, siblingPath);
vm.stopPrank();
}
function testWithdrawWithDesignatedCallerSucceedsForDesignatedCaller() public {
// add message with caller as this address
(bytes32 l2ToL1Message, bytes32[] memory siblingPath, bytes32 treeRoot) =
_addWithdrawMessageInOutbox(address(this), l2BlockNumber);
vm.expectEmit(true, true, true, true);
emit IOutbox.MessageConsumed(l2BlockNumber, treeRoot, l2ToL1Message, 0);
tokenPortal.withdraw(recipient, withdrawAmount, true, l2BlockNumber, 0, siblingPath);
// Should have received 654 RNA tokens
assertEq(portalERC20.balanceOf(recipient), withdrawAmount);
}
}