-
Notifications
You must be signed in to change notification settings - Fork 1
/
RoutersFacet.sol
605 lines (512 loc) · 24 KB
/
RoutersFacet.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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.14;
import {BaseConnextFacet} from "./BaseConnextFacet.sol";
import {AssetLogic} from "../libraries/AssetLogic.sol";
import {AppStorage} from "../libraries/LibConnextStorage.sol";
/**
* @notice
* This contract is designed to manage router access, meaning it maintains the
* router recipients, owners, and the router whitelist itself.
*
* As a router, there are three important permissions:
* `router` - this is the address that will sign bids sent to the sequencer
* `routerRecipient` - this is the address that receives funds when liquidity is withdrawn
* `routerOwner` - this is the address permitted to update recipients and propose new owners
*
* In cases where the owner is not set, the caller should be the `router` itself. In cases where the
* `routerRecipient` is not set, the funds can be removed to anywhere.
*
* When setting a new `routerOwner`, the current owner (or router) must create a proposal, which
* can be accepted by the proposed owner after the delay period. If the proposed owner is the empty
* address, then it must be accepted by the current owner.
*/
contract RoutersFacet is BaseConnextFacet {
// ========== Custom Errors ===========
error RoutersFacet__acceptProposedRouterOwner_notElapsed();
error RoutersFacet__setRouterRecipient_notNewRecipient();
error RoutersFacet__onlyRouterOwner_notRouterOwner();
error RoutersFacet__onlyProposedRouterOwner_notRouterOwner();
error RoutersFacet__onlyProposedRouterOwner_notProposedRouterOwner();
error RoutersFacet__removeRouter_routerEmpty();
error RoutersFacet__removeRouter_notAdded();
error RoutersFacet__setupRouter_routerEmpty();
error RoutersFacet__setupRouter_alreadyAdded();
error RoutersFacet__proposeRouterOwner_notNewOwner();
error RoutersFacet__proposeRouterOwner_badRouter();
error RoutersFacet__setMaxRoutersPerTransfer_invalidMaxRoutersPerTransfer();
error RoutersFacet__addLiquidityForRouter_routerEmpty();
error RoutersFacet__addLiquidityForRouter_amountIsZero();
error RoutersFacet__addLiquidityForRouter_badRouter();
error RoutersFacet__addLiquidityForRouter_badAsset();
error RoutersFacet__removeRouterLiquidity_recipientEmpty();
error RoutersFacet__removeRouterLiquidity_amountIsZero();
error RoutersFacet__removeRouterLiquidity_insufficientFunds();
error RoutersFacet__removeRouterLiquidityFor_notOwner();
error RoutersFacet__setLiquidityFeeNumerator_tooSmall();
error RoutersFacet__setLiquidityFeeNumerator_tooLarge();
error RoutersFacet__approveRouterForPortal_notRouter();
error RoutersFacet__approveRouterForPortal_alreadyApproved();
error RoutersFacet__unapproveRouterForPortal_notApproved();
// ============ Properties ============
// ============ Constants ============
uint256 private constant _delay = 7 days;
// ============ Events ============
/**
* @notice Emitted when a new router is added
* @param router - The address of the added router
* @param caller - The account that called the function
*/
event RouterAdded(address indexed router, address caller);
/**
* @notice Emitted when an existing router is removed
* @param router - The address of the removed router
* @param caller - The account that called the function
*/
event RouterRemoved(address indexed router, address caller);
/**
* @notice Emitted when the recipient of router is updated
* @param router - The address of the added router
* @param prevRecipient - The address of the previous recipient of the router
* @param newRecipient - The address of the new recipient of the router
*/
event RouterRecipientSet(address indexed router, address indexed prevRecipient, address indexed newRecipient);
/**
* @notice Emitted when the owner of router is proposed
* @param router - The address of the added router
* @param prevProposed - The address of the previous proposed
* @param newProposed - The address of the new proposed
*/
event RouterOwnerProposed(address indexed router, address indexed prevProposed, address indexed newProposed);
/**
* @notice Emitted when the owner of router is accepted
* @param router - The address of the added router
* @param prevOwner - The address of the previous owner of the router
* @param newOwner - The address of the new owner of the router
*/
event RouterOwnerAccepted(address indexed router, address indexed prevOwner, address indexed newOwner);
/**
* @notice Emitted when the maxRoutersPerTransfer variable is updated
* @param maxRoutersPerTransfer - The maxRoutersPerTransfer new value
* @param caller - The account that called the function
*/
event MaxRoutersPerTransferUpdated(uint256 maxRoutersPerTransfer, address caller);
/**
* @notice Emitted when the LIQUIDITY_FEE_NUMERATOR variable is updated
* @param liquidityFeeNumerator - The LIQUIDITY_FEE_NUMERATOR new value
* @param caller - The account that called the function
*/
event LiquidityFeeNumeratorUpdated(uint256 liquidityFeeNumerator, address caller);
/**
* @notice Emitted when a router is approved for Portal
* @param router - The address of the approved router
* @param caller - The account that called the function
*/
event RouterApprovedForPortal(address router, address caller);
/**
* @notice Emitted when a router is disapproved for Portal
* @param router - The address of the disapproved router
* @param caller - The account that called the function
*/
event RouterUnapprovedForPortal(address router, address caller);
/**
* @notice Emitted when a router adds liquidity to the contract
* @param router - The address of the router the funds were credited to
* @param local - The address of the token added (all liquidity held in local asset)
* @param amount - The amount of liquidity added
* @param caller - The account that called the function
*/
event RouterLiquidityAdded(
address indexed router,
address local,
bytes32 canonicalId,
uint256 amount,
address caller
);
/**
* @notice Emitted when a router withdraws liquidity from the contract
* @param router - The router you are removing liquidity from
* @param to - The address the funds were withdrawn to
* @param local - The address of the token withdrawn
* @param amount - The amount of liquidity withdrawn
* @param caller - The account that called the function
*/
event RouterLiquidityRemoved(address indexed router, address to, address local, uint256 amount, address caller);
// ============ Modifiers ============
/**
* @notice Asserts caller is the router owner (if set) or the router itself
*/
modifier onlyRouterOwner(address _router) {
address owner = s.routerPermissionInfo.routerOwners[_router];
if (!((owner == address(0) && msg.sender == _router) || owner == msg.sender))
revert RoutersFacet__onlyRouterOwner_notRouterOwner();
_;
}
/**
* @notice Asserts caller is the proposed router. If proposed router is address(0), then asserts
* the owner is calling the function (if set), or the router itself is calling the function
*/
modifier onlyProposedRouterOwner(address _router) {
address proposed = s.routerPermissionInfo.proposedRouterOwners[_router];
if (proposed == address(0)) {
address owner = s.routerPermissionInfo.routerOwners[_router];
if (!((owner == address(0) && msg.sender == _router) || owner == msg.sender))
revert RoutersFacet__onlyProposedRouterOwner_notRouterOwner();
} else {
if (msg.sender != proposed) revert RoutersFacet__onlyProposedRouterOwner_notProposedRouterOwner();
}
_;
}
// ============ Getters ==============
function LIQUIDITY_FEE_NUMERATOR() public view returns (uint256) {
return s.LIQUIDITY_FEE_NUMERATOR;
}
function LIQUIDITY_FEE_DENOMINATOR() public view returns (uint256) {
return s.LIQUIDITY_FEE_DENOMINATOR;
}
/**
* @notice Returns the approved router for the given router address
* @param _router The relevant router address
*/
function getRouterApproval(address _router) public view returns (bool) {
return s.routerPermissionInfo.approvedRouters[_router];
}
/**
* @notice Returns the recipient for the specified router
* @dev The recipient (if set) receives all funds when router liquidity is removed
* @param _router The relevant router address
*/
function getRouterRecipient(address _router) public view returns (address) {
return s.routerPermissionInfo.routerRecipients[_router];
}
/**
* @notice Returns the router owner if it is set, or the router itself if not
* @dev Uses logic function here to handle the case where router owner is not set.
* Other getters within this interface use explicitly the stored value
* @param _router The relevant router address
*/
function getRouterOwner(address _router) public view returns (address) {
address _owner = s.routerPermissionInfo.routerOwners[_router];
return _owner == address(0) ? _router : _owner;
}
/**
* @notice Returns the currently proposed router owner
* @dev All routers must wait for the delay timeout before accepting a new owner
* @param _router The relevant router address
*/
function getProposedRouterOwner(address _router) public view returns (address) {
return s.routerPermissionInfo.proposedRouterOwners[_router];
}
/**
* @notice Returns the currently proposed router owner timestamp
* @dev All routers must wait for the delay timeout before accepting a new owner
* @param _router The relevant router address
*/
function getProposedRouterOwnerTimestamp(address _router) public view returns (uint256) {
return s.routerPermissionInfo.proposedRouterTimestamp[_router];
}
function maxRoutersPerTransfer() public view returns (uint256) {
return s.maxRoutersPerTransfer;
}
function routerBalances(address _router, address _asset) public view returns (uint256) {
return s.routerBalances[_router][_asset];
}
/**
* @notice Returns whether the router is approved for portals or not
* @param _router The relevant router address
*/
function getRouterApprovalForPortal(address _router) public view returns (bool) {
return s.routerPermissionInfo.approvedForPortalRouters[_router];
}
// ============ Admin methods ==============
/**
* @notice Used to set router initial properties
* @param router Router address to setup
* @param owner Initial Owner of router
* @param recipient Initial Recipient of router
*/
function setupRouter(
address router,
address owner,
address recipient
) external onlyOwner {
// Sanity check: not empty
if (router == address(0)) revert RoutersFacet__setupRouter_routerEmpty();
// Sanity check: needs approval
if (s.routerPermissionInfo.approvedRouters[router]) revert RoutersFacet__setupRouter_alreadyAdded();
// Approve router
s.routerPermissionInfo.approvedRouters[router] = true;
// Emit event
emit RouterAdded(router, msg.sender);
// Update routerOwner (zero address possible)
if (owner != address(0)) {
s.routerPermissionInfo.routerOwners[router] = owner;
emit RouterOwnerAccepted(router, address(0), owner);
}
// Update router recipient
if (recipient != address(0)) {
s.routerPermissionInfo.routerRecipients[router] = recipient;
emit RouterRecipientSet(router, address(0), recipient);
}
}
/**
* @notice Used to remove routers that can transact crosschain
* @param router Router address to remove
*/
function removeRouter(address router) external onlyOwner {
// Sanity check: not empty
if (router == address(0)) revert RoutersFacet__removeRouter_routerEmpty();
// Sanity check: needs removal
if (!s.routerPermissionInfo.approvedRouters[router]) revert RoutersFacet__removeRouter_notAdded();
// Update mapping
s.routerPermissionInfo.approvedRouters[router] = false;
// Emit event
emit RouterRemoved(router, msg.sender);
// Remove router owner
address _owner = s.routerPermissionInfo.routerOwners[router];
if (_owner != address(0)) {
emit RouterOwnerAccepted(router, _owner, address(0));
// delete routerOwners[router];
s.routerPermissionInfo.routerOwners[router] = address(0);
}
// Remove router recipient
address _recipient = s.routerPermissionInfo.routerRecipients[router];
if (_recipient != address(0)) {
emit RouterRecipientSet(router, _recipient, address(0));
// delete routerRecipients[router];
s.routerPermissionInfo.routerRecipients[router] = address(0);
}
// Clear any proposed ownership changes
s.routerPermissionInfo.proposedRouterOwners[router] = address(0);
s.routerPermissionInfo.proposedRouterTimestamp[router] = 0;
}
/**
* @notice Used to set the max amount of routers a payment can be routed through
* @param _newMaxRouters The new max amount of routers
*/
function setMaxRoutersPerTransfer(uint256 _newMaxRouters) external onlyOwner {
if (_newMaxRouters == 0 || _newMaxRouters == s.maxRoutersPerTransfer)
revert RoutersFacet__setMaxRoutersPerTransfer_invalidMaxRoutersPerTransfer();
emit MaxRoutersPerTransferUpdated(_newMaxRouters, msg.sender);
s.maxRoutersPerTransfer = _newMaxRouters;
}
/**
* @notice Sets the LIQUIDITY_FEE_NUMERATOR
* @dev Admin can set LIQUIDITY_FEE_NUMERATOR variable, Liquidity fee should be less than 5%
* @param _numerator new LIQUIDITY_FEE_NUMERATOR
*/
function setLiquidityFeeNumerator(uint256 _numerator) external onlyOwner {
// Slightly misleading: the liquidity fee numerator is not the amount charged,
// but the amount received after fees are deducted (e.g. 9995/10000 would be .005%).
uint256 denominator = s.LIQUIDITY_FEE_DENOMINATOR;
if (_numerator < (denominator * 95) / 100) revert RoutersFacet__setLiquidityFeeNumerator_tooSmall();
if (_numerator > denominator) revert RoutersFacet__setLiquidityFeeNumerator_tooLarge();
s.LIQUIDITY_FEE_NUMERATOR = _numerator;
emit LiquidityFeeNumeratorUpdated(_numerator, msg.sender);
}
/**
* @notice Allow router to use Portals
* @param _router - The router address to approve
*/
function approveRouterForPortal(address _router) external onlyOwner {
if (!s.routerPermissionInfo.approvedRouters[_router]) revert RoutersFacet__approveRouterForPortal_notRouter();
if (s.routerPermissionInfo.approvedForPortalRouters[_router])
revert RoutersFacet__approveRouterForPortal_alreadyApproved();
s.routerPermissionInfo.approvedForPortalRouters[_router] = true;
emit RouterApprovedForPortal(_router, msg.sender);
}
/**
* @notice Remove router access to use Portals
* @param _router - The router address to remove approval
*/
function unapproveRouterForPortal(address _router) external onlyOwner {
if (!s.routerPermissionInfo.approvedForPortalRouters[_router])
revert RoutersFacet__unapproveRouterForPortal_notApproved();
s.routerPermissionInfo.approvedForPortalRouters[_router] = false;
emit RouterUnapprovedForPortal(_router, msg.sender);
}
// ============ Public methods ==============
/**
* @notice Sets the designated recipient for a router
* @dev Router should only be able to set this once otherwise if router key compromised,
* no problem is solved since attacker could just update recipient
* @param router Router address to set recipient
* @param recipient Recipient Address to set to router
*/
function setRouterRecipient(address router, address recipient) external onlyRouterOwner(router) {
// Check recipient is changing
address _prevRecipient = s.routerPermissionInfo.routerRecipients[router];
if (_prevRecipient == recipient) revert RoutersFacet__setRouterRecipient_notNewRecipient();
// Set new recipient
s.routerPermissionInfo.routerRecipients[router] = recipient;
// Emit event
emit RouterRecipientSet(router, _prevRecipient, recipient);
}
/**
* @notice Current owner or router may propose a new router owner
* @param router Router address to set recipient
* @param proposed Proposed owner Address to set to router
*/
function proposeRouterOwner(address router, address proposed) external onlyRouterOwner(router) {
// Check that proposed is different than current owner
if (getRouterOwner(router) == proposed) revert RoutersFacet__proposeRouterOwner_notNewOwner();
// Check that proposed is different than current proposed
address _currentProposed = s.routerPermissionInfo.proposedRouterOwners[router];
if (_currentProposed == proposed) revert RoutersFacet__proposeRouterOwner_badRouter();
// Set proposed owner + timestamp
s.routerPermissionInfo.proposedRouterOwners[router] = proposed;
s.routerPermissionInfo.proposedRouterTimestamp[router] = block.timestamp;
// Emit event
emit RouterOwnerProposed(router, _currentProposed, proposed);
}
/**
* @notice New router owner must accept role, or previous if proposed is 0x0
* @param router Router address to set recipient
*/
function acceptProposedRouterOwner(address router) external onlyProposedRouterOwner(router) {
address owner = getRouterOwner(router);
// Check timestamp has passed
if (block.timestamp - s.routerPermissionInfo.proposedRouterTimestamp[router] <= _delay)
revert RoutersFacet__acceptProposedRouterOwner_notElapsed();
// Get current owner + proposed
address _proposed = s.routerPermissionInfo.proposedRouterOwners[router];
// Update the current owner
s.routerPermissionInfo.routerOwners[router] = _proposed;
// Reset proposal + timestamp
if (_proposed != address(0)) {
s.routerPermissionInfo.proposedRouterOwners[router] = address(0);
}
s.routerPermissionInfo.proposedRouterTimestamp[router] = 0;
// Emit event
emit RouterOwnerAccepted(router, owner, _proposed);
}
/**
* @notice This is used by anyone to increase a router's available liquidity for a given asset.
* @dev The liquidity will be held in the local asset, which is the representation if you
* are *not* on the canonical domain, and the canonical asset otherwise.
* @param _amount - The amount of liquidity to add for the router
* @param _local - The address of the asset you're adding liquidity for. If adding liquidity of the
* native asset, routers may use `address(0)` or the wrapped asset
* @param _router The router you are adding liquidity on behalf of
*/
function addRouterLiquidityFor(
uint256 _amount,
address _local,
address _router
) external payable nonReentrant whenNotPaused {
_addLiquidityForRouter(_amount, _local, _router);
}
/**
* @notice This is used by any router to increase their available liquidity for a given asset.
* @dev The liquidity will be held in the local asset, which is the representation if you
* are *not* on the canonical domain, and the canonical asset otherwise.
* @param _amount - The amount of liquidity to add for the router
* @param _local - The address of the asset you're adding liquidity for. If adding liquidity of the
* native asset, routers may use `address(0)` or the wrapped asset
*/
function addRouterLiquidity(uint256 _amount, address _local) external payable nonReentrant whenNotPaused {
_addLiquidityForRouter(_amount, _local, msg.sender);
}
/**
* @notice This is used by any router owner to decrease their available liquidity for a given asset.
* @param _amount - The amount of liquidity to remove for the router
* @param _local - The address of the asset you're removing liquidity from. If removing liquidity of the
* native asset, routers may use `address(0)` or the wrapped asset
* @param _to The address that will receive the liquidity being removed
* @param _router The address of the router
*/
function removeRouterLiquidityFor(
uint256 _amount,
address _local,
address payable _to,
address _router
) external nonReentrant whenNotPaused {
// Caller must be the router owner
if (msg.sender != getRouterOwner(_router)) revert RoutersFacet__removeRouterLiquidityFor_notOwner();
// Remove liquidity
_removeLiquidityForRouter(_amount, _local, _to, _router);
}
/**
* @notice This is used by any router to decrease their available liquidity for a given asset.
* @param _amount - The amount of liquidity to remove for the router
* @param _local - The address of the asset you're removing liquidity from. If removing liquidity of the
* native asset, routers may use `address(0)` or the wrapped asset
* @param _to The address that will receive the liquidity being removed if no router recipient exists.
*/
function removeRouterLiquidity(
uint256 _amount,
address _local,
address payable _to
) external nonReentrant whenNotPaused {
_removeLiquidityForRouter(_amount, _local, _to, msg.sender);
}
// ============ Internal functions ============
/**
* @notice Contains the logic to verify + increment a given routers liquidity
* @dev The liquidity will be held in the local asset, which is the representation if you
* are *not* on the canonical domain, and the canonical asset otherwise.
* @param _amount - The amount of liquidity to add for the router
* @param _local - The address of the nomad representation of the asset
* @param _router - The router you are adding liquidity on behalf of
*/
function _addLiquidityForRouter(
uint256 _amount,
address _local,
address _router
) internal {
// Sanity check: router is sensible
if (_router == address(0)) revert RoutersFacet__addLiquidityForRouter_routerEmpty();
// Sanity check: nonzero amounts
if (_amount == 0) revert RoutersFacet__addLiquidityForRouter_amountIsZero();
// Get the canonical asset id from the representation
(, bytes32 canonicalId) = s.tokenRegistry.getTokenId(_local == address(0) ? address(s.wrapper) : _local);
// Router is approved
if (!_isRouterOwnershipRenounced() && !getRouterApproval(_router))
revert RoutersFacet__addLiquidityForRouter_badRouter();
// Asset is approved
if (!_isAssetOwnershipRenounced() && !s.approvedAssets[canonicalId])
revert RoutersFacet__addLiquidityForRouter_badAsset();
// Transfer funds to contract
(address asset, uint256 received) = AssetLogic.handleIncomingAsset(_local, _amount, 0);
// Update the router balances. Happens after pulling funds to account for
// the fee on transfer tokens
s.routerBalances[_router][asset] += received;
// Emit event
emit RouterLiquidityAdded(_router, asset, canonicalId, received, msg.sender);
}
/**
* @notice This is used by any router owner to decrease their available liquidity for a given asset.
* @param _amount - The amount of liquidity to remove for the router
* @param _local - The address of the asset you're removing liquidity from. If removing liquidity of the
* native asset, routers may use `address(0)` or the wrapped asset
* @param _to The address that will receive the liquidity being removed
* @param _router The address of the router
*/
function _removeLiquidityForRouter(
uint256 _amount,
address _local,
address payable _to,
address _router
) internal {
// transfer to specicfied recipient IF recipient not set
address recipient = getRouterRecipient(_router);
recipient = recipient == address(0) ? _to : recipient;
// Sanity check: to is sensible
if (recipient == address(0)) revert RoutersFacet__removeRouterLiquidity_recipientEmpty();
// Sanity check: nonzero amounts
if (_amount == 0) revert RoutersFacet__removeRouterLiquidity_amountIsZero();
// Get the local key
address key = _local == address(0) ? address(s.wrapper) : _local;
// Get existing router balance
uint256 routerBalance = s.routerBalances[_router][key];
// Sanity check: amount can be deducted for the router
if (routerBalance < _amount) revert RoutersFacet__removeRouterLiquidity_insufficientFunds();
// Update router balances
unchecked {
s.routerBalances[_router][key] = routerBalance - _amount;
}
// Transfer from contract to specified to
AssetLogic.transferAssetFromContract(key, recipient, _amount);
// Emit event
emit RouterLiquidityRemoved(_router, recipient, _local, _amount, msg.sender);
}
}