diff --git a/packages/horizon/contracts/interfaces/IPaymentsEscrow.sol b/packages/horizon/contracts/interfaces/IPaymentsEscrow.sol index b480f3c31..c1eb7707a 100644 --- a/packages/horizon/contracts/interfaces/IPaymentsEscrow.sol +++ b/packages/horizon/contracts/interfaces/IPaymentsEscrow.sol @@ -15,9 +15,9 @@ import { IGraphPayments } from "./IGraphPayments.sol"; * collector contract which implements the {IPaymentsCollector} interface. */ interface IPaymentsEscrow { - /// @notice Escrow account for a payer-receiver pair + /// @notice Escrow account for a payer-collector-receiver tuple struct EscrowAccount { - // Total token balance for the payer-receiver pair + // Total token balance for the payer-collector-receiver tuple uint256 balance; // Amount of tokens currently being thawed uint256 tokensThawing; @@ -70,12 +70,13 @@ interface IPaymentsEscrow { event RevokeCollector(address indexed payer, address indexed collector); /** - * @notice Emitted when a payer deposits funds into the escrow for a payer-receiver pair + * @notice Emitted when a payer deposits funds into the escrow for a payer-collector-receiver tuple * @param payer The address of the payer + * @param collector The address of the collector * @param receiver The address of the receiver * @param tokens The amount of tokens deposited */ - event Deposit(address indexed payer, address indexed receiver, uint256 tokens); + event Deposit(address indexed payer, address indexed collector, address indexed receiver, uint256 tokens); /** * @notice Emitted when a payer cancels an escrow thawing @@ -85,29 +86,38 @@ interface IPaymentsEscrow { event CancelThaw(address indexed payer, address indexed receiver); /** - * @notice Emitted when a payer thaws funds from the escrow for a payer-receiver pair + * @notice Emitted when a payer thaws funds from the escrow for a payer-collector-receiver tuple * @param payer The address of the payer + * @param collector The address of the collector * @param receiver The address of the receiver * @param tokens The amount of tokens being thawed * @param thawEndTimestamp The timestamp at which the thawing period ends */ - event Thaw(address indexed payer, address indexed receiver, uint256 tokens, uint256 thawEndTimestamp); + event Thaw( + address indexed payer, + address indexed collector, + address indexed receiver, + uint256 tokens, + uint256 thawEndTimestamp + ); /** - * @notice Emitted when a payer withdraws funds from the escrow for a payer-receiver pair + * @notice Emitted when a payer withdraws funds from the escrow for a payer-collector-receiver tuple * @param payer The address of the payer + * @param collector The address of the collector * @param receiver The address of the receiver * @param tokens The amount of tokens withdrawn */ - event Withdraw(address indexed payer, address indexed receiver, uint256 tokens); + event Withdraw(address indexed payer, address indexed collector, address indexed receiver, uint256 tokens); /** - * @notice Emitted when a collector collects funds from the escrow for a payer-receiver pair + * @notice Emitted when a collector collects funds from the escrow for a payer-collector-receiver tuple * @param payer The address of the payer + * @param collector The address of the collector * @param receiver The address of the receiver * @param tokens The amount of tokens collected */ - event EscrowCollected(address indexed payer, address indexed receiver, uint256 tokens); + event EscrowCollected(address indexed payer, address indexed collector, address indexed receiver, uint256 tokens); // -- Errors -- @@ -211,26 +221,28 @@ interface IPaymentsEscrow { function revokeCollector(address collector) external; /** - * @notice Deposits funds into the escrow for a payer-receiver pair, where + * @notice Deposits funds into the escrow for a payer-collector-receiver tuple, where * the payer is the transaction caller. * @dev Emits a {Deposit} event + * @param collector The address of the collector * @param receiver The address of the receiver * @param tokens The amount of tokens to deposit */ - function deposit(address receiver, uint256 tokens) external; + function deposit(address collector, address receiver, uint256 tokens) external; /** - * @notice Deposits funds into the escrow for a payer-receiver pair, where + * @notice Deposits funds into the escrow for a payer-collector-receiver tuple, where * the payer can be specified. * @dev Emits a {Deposit} event * @param payer The address of the payer + * @param collector The address of the collector * @param receiver The address of the receiver * @param tokens The amount of tokens to deposit */ - function depositTo(address payer, address receiver, uint256 tokens) external; + function depositTo(address payer, address collector, address receiver, uint256 tokens) external; /** - * @notice Thaw a specific amount of escrow from a payer-receiver's escrow account. + * @notice Thaw a specific amount of escrow from a payer-collector-receiver's escrow account. * The payer is the transaction caller. * If `tokens` is zero and funds were already thawing it will cancel the thawing. * Note that repeated calls to this function will overwrite the previous thawing amount @@ -240,13 +252,14 @@ interface IPaymentsEscrow { * * Emits a {Thaw} event. If `tokens` is zero it will emit a {CancelThaw} event. * + * @param collector The address of the collector * @param receiver The address of the receiver * @param tokens The amount of tokens to thaw */ - function thaw(address receiver, uint256 tokens) external; + function thaw(address collector, address receiver, uint256 tokens) external; /** - * @notice Withdraws all thawed escrow from a payer-receiver's escrow account. + * @notice Withdraws all thawed escrow from a payer-collector-receiver's escrow account. * The payer is the transaction caller. * Note that the withdrawn funds might be less than the thawed amount if there were * payment collections in the meantime. @@ -255,12 +268,13 @@ interface IPaymentsEscrow { * * Emits a {Withdraw} event * + * @param collector The address of the collector * @param receiver The address of the receiver */ - function withdraw(address receiver) external; + function withdraw(address collector, address receiver) external; /** - * @notice Collects funds from the payer-receiver's escrow and sends them to {GraphPayments} for + * @notice Collects funds from the payer-collector-receiver's escrow and sends them to {GraphPayments} for * distribution using the Graph Horizon Payments protocol. * The function will revert if there are not enough funds in the escrow. * @dev Requirements: @@ -272,7 +286,7 @@ interface IPaymentsEscrow { * @param payer The address of the payer * @param receiver The address of the receiver * @param tokens The amount of tokens to collect - * @param collector The address of the collector + * @param dataService The address of the data service * @param tokensDataService The amount of tokens that {GraphPayments} should send to the data service */ function collect( @@ -280,14 +294,15 @@ interface IPaymentsEscrow { address payer, address receiver, uint256 tokens, - address collector, + address dataService, uint256 tokensDataService ) external; /** - * @notice Get the balance of a payer-receiver pair + * @notice Get the balance of a payer-collector-receiver tuple * @param payer The address of the payer + * @param collector The address of the collector * @param receiver The address of the receiver */ - function getBalance(address payer, address receiver) external view returns (uint256); + function getBalance(address payer, address collector, address receiver) external view returns (uint256); } diff --git a/packages/horizon/contracts/payments/PaymentsEscrow.sol b/packages/horizon/contracts/payments/PaymentsEscrow.sol index bd6bd0ad1..7cf7e9e38 100644 --- a/packages/horizon/contracts/payments/PaymentsEscrow.sol +++ b/packages/horizon/contracts/payments/PaymentsEscrow.sol @@ -27,11 +27,11 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory, mapping(address payer => mapping(address collector => IPaymentsEscrow.Collector collectorDetails)) public authorizedCollectors; - /// @notice Escrow account details for payer-receiver pairs - mapping(address payer => mapping(address receiver => IPaymentsEscrow.EscrowAccount escrowAccount)) + /// @notice Escrow account details for payer-collector-receiver tuples + mapping(address payer => mapping(address collector => mapping(address receiver => IPaymentsEscrow.EscrowAccount escrowAccount))) public escrowAccounts; - /// @notice The maximum thawing period (in seconds) for both escrow withdrawal and signer revocation + /// @notice The maximum thawing period (in seconds) for both escrow withdrawal and collector revocation /// @dev This is a precautionary measure to avoid inadvertedly locking funds for too long uint256 public constant MAX_WAIT_PERIOD = 90 days; @@ -126,22 +126,22 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory, /** * @notice See {IPaymentsEscrow-deposit} */ - function deposit(address receiver, uint256 tokens) external override notPaused { - _deposit(msg.sender, receiver, tokens); + function deposit(address collector, address receiver, uint256 tokens) external override notPaused { + _deposit(msg.sender, collector, receiver, tokens); } /** * @notice See {IPaymentsEscrow-depositTo} */ - function depositTo(address payer, address receiver, uint256 tokens) external override notPaused { - _deposit(payer, receiver, tokens); + function depositTo(address payer, address collector, address receiver, uint256 tokens) external override notPaused { + _deposit(payer, collector, receiver, tokens); } /** * @notice See {IPaymentsEscrow-thaw} */ - function thaw(address receiver, uint256 tokens) external override notPaused { - EscrowAccount storage account = escrowAccounts[msg.sender][receiver]; + function thaw(address collector, address receiver, uint256 tokens) external override notPaused { + EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver]; // if amount thawing is zero and requested amount is zero this is an invalid request. // otherwise if amount thawing is greater than zero and requested amount is zero this @@ -159,14 +159,14 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory, account.tokensThawing = tokens; account.thawEndTimestamp = block.timestamp + WITHDRAW_ESCROW_THAWING_PERIOD; - emit Thaw(msg.sender, receiver, tokens, account.thawEndTimestamp); + emit Thaw(msg.sender, collector, receiver, tokens, account.thawEndTimestamp); } /** * @notice See {IPaymentsEscrow-withdraw} */ - function withdraw(address receiver) external override notPaused { - EscrowAccount storage account = escrowAccounts[msg.sender][receiver]; + function withdraw(address collector, address receiver) external override notPaused { + EscrowAccount storage account = escrowAccounts[msg.sender][collector][receiver]; require(account.thawEndTimestamp != 0, PaymentsEscrowNotThawing()); require( account.thawEndTimestamp < block.timestamp, @@ -180,7 +180,7 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory, account.tokensThawing = 0; account.thawEndTimestamp = 0; _graphToken().pushTokens(msg.sender, tokens); - emit Withdraw(msg.sender, receiver, tokens); + emit Withdraw(msg.sender, collector, receiver, tokens); } /** @@ -195,15 +195,18 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory, uint256 tokensDataService ) external override notPaused { // Check if collector is authorized and has enough funds - Collector storage collector = authorizedCollectors[payer][msg.sender]; - require(collector.allowance >= tokens, PaymentsEscrowInsufficientAllowance(collector.allowance, tokens)); + Collector storage collectorDetails = authorizedCollectors[payer][msg.sender]; + require( + collectorDetails.allowance >= tokens, + PaymentsEscrowInsufficientAllowance(collectorDetails.allowance, tokens) + ); // Check if there are enough funds in the escrow account - EscrowAccount storage account = escrowAccounts[payer][receiver]; + EscrowAccount storage account = escrowAccounts[payer][msg.sender][receiver]; require(account.balance >= tokens, PaymentsEscrowInsufficientBalance(account.balance, tokens)); // Reduce amount from approved collector and account balance - collector.allowance -= tokens; + collectorDetails.allowance -= tokens; account.balance -= tokens; uint256 balanceBefore = _graphToken().balanceOf(address(this)); @@ -217,14 +220,14 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory, PaymentsEscrowInconsistentCollection(balanceBefore, balanceAfter, tokens) ); - emit EscrowCollected(payer, receiver, tokens); + emit EscrowCollected(payer, msg.sender, receiver, tokens); } /** * @notice See {IPaymentsEscrow-getBalance} */ - function getBalance(address payer, address receiver) external view override returns (uint256) { - EscrowAccount storage account = escrowAccounts[payer][receiver]; + function getBalance(address payer, address collector, address receiver) external view override returns (uint256) { + EscrowAccount storage account = escrowAccounts[payer][collector][receiver]; return account.balance - account.tokensThawing; } @@ -234,9 +237,9 @@ contract PaymentsEscrow is Initializable, MulticallUpgradeable, GraphDirectory, * @param _receiver The address of the receiver * @param _tokens The amount of tokens to deposit */ - function _deposit(address _payer, address _receiver, uint256 _tokens) private { - escrowAccounts[_payer][_receiver].balance += _tokens; + function _deposit(address _payer, address _collector, address _receiver, uint256 _tokens) private { + escrowAccounts[_payer][_collector][_receiver].balance += _tokens; _graphToken().pullTokens(msg.sender, _tokens); - emit Deposit(_payer, _receiver, _tokens); + emit Deposit(_payer, _collector, _receiver, _tokens); } } diff --git a/packages/horizon/test/escrow/GraphEscrow.t.sol b/packages/horizon/test/escrow/GraphEscrow.t.sol index 2421ea80c..75d0b55ae 100644 --- a/packages/horizon/test/escrow/GraphEscrow.t.sol +++ b/packages/horizon/test/escrow/GraphEscrow.t.sol @@ -39,7 +39,7 @@ contract GraphEscrowTest is HorizonStakingSharedTest { vm.assume(thawAmount > 0); vm.assume(amount > thawAmount); _depositTokens(amount); - escrow.thaw(users.indexer, thawAmount); + escrow.thaw(users.verifier, users.indexer, thawAmount); _; } @@ -49,7 +49,7 @@ contract GraphEscrowTest is HorizonStakingSharedTest { function _depositTokens(uint256 tokens) internal { token.approve(address(escrow), tokens); - escrow.deposit(users.indexer, tokens); + escrow.deposit(users.verifier, users.indexer, tokens); } function _approveEscrow(uint256 tokens) internal { diff --git a/packages/horizon/test/escrow/collect.t.sol b/packages/horizon/test/escrow/collect.t.sol index d9b775b93..db0c2c099 100644 --- a/packages/horizon/test/escrow/collect.t.sol +++ b/packages/horizon/test/escrow/collect.t.sol @@ -27,8 +27,10 @@ contract GraphEscrowCollectTest is GraphEscrowTest { address _dataService, uint256 _tokensDataService ) private { + (, address _collector, ) = vm.readCallers(); + // Previous balances - (uint256 previousPayerEscrowBalance,,) = escrow.escrowAccounts(_payer, _receiver); + (uint256 previousPayerEscrowBalance,,) = escrow.escrowAccounts(_payer, _collector, _receiver); CollectPaymentData memory previousBalances = CollectPaymentData({ escrowBalance: token.balanceOf(address(escrow)), paymentsBalance: token.balanceOf(address(payments)), @@ -41,7 +43,7 @@ contract GraphEscrowCollectTest is GraphEscrowTest { }); vm.expectEmit(address(escrow)); - emit IPaymentsEscrow.EscrowCollected(_payer, _receiver, _tokens); + emit IPaymentsEscrow.EscrowCollected(_payer, _collector, _receiver, _tokens); escrow.collect(_paymentType, _payer, _receiver, _tokens, _dataService, _tokensDataService); // Calculate cuts @@ -51,11 +53,9 @@ contract GraphEscrowCollectTest is GraphEscrowTest { _dataService, _paymentType ); - uint256 tokensProtocol = _tokens * protocolPaymentCut / MAX_PPM; - uint256 tokensDelegation = _tokens * delegatorCut / MAX_PPM; // After balances - (uint256 afterPayerEscrowBalance,,) = escrow.escrowAccounts(_payer, _receiver); + (uint256 afterPayerEscrowBalance,,) = escrow.escrowAccounts(_payer, _collector, _receiver); CollectPaymentData memory afterBalances = CollectPaymentData({ escrowBalance: token.balanceOf(address(escrow)), paymentsBalance: token.balanceOf(address(payments)), @@ -68,12 +68,12 @@ contract GraphEscrowCollectTest is GraphEscrowTest { }); // Check receiver balance after payment - uint256 receiverExpectedPayment = _tokens - _tokensDataService - tokensProtocol - tokensDelegation; + uint256 receiverExpectedPayment = _tokens - _tokensDataService - _tokens * protocolPaymentCut / MAX_PPM - _tokens * delegatorCut / MAX_PPM; assertEq(afterBalances.receiverBalance - previousBalances.receiverBalance, receiverExpectedPayment); assertEq(token.balanceOf(address(payments)), 0); // Check delegation pool balance after payment - assertEq(afterBalances.delegationPoolBalance - previousBalances.delegationPoolBalance, tokensDelegation); + assertEq(afterBalances.delegationPoolBalance - previousBalances.delegationPoolBalance, _tokens * delegatorCut / MAX_PPM); // Check that the escrow account has been updated assertEq(previousBalances.escrowBalance, afterBalances.escrowBalance + _tokens); diff --git a/packages/horizon/test/escrow/deposit.t.sol b/packages/horizon/test/escrow/deposit.t.sol index 3ce341a1a..002d2b1a4 100644 --- a/packages/horizon/test/escrow/deposit.t.sol +++ b/packages/horizon/test/escrow/deposit.t.sol @@ -12,7 +12,7 @@ contract GraphEscrowDepositTest is GraphEscrowTest { */ function testDeposit_Tokens(uint256 amount) public useGateway useDeposit(amount) { - (uint256 indexerEscrowBalance,,) = escrow.escrowAccounts(users.gateway, users.indexer); + (uint256 indexerEscrowBalance,,) = escrow.escrowAccounts(users.gateway, users.verifier, users.indexer); assertEq(indexerEscrowBalance, amount); } } \ No newline at end of file diff --git a/packages/horizon/test/escrow/paused.t.sol b/packages/horizon/test/escrow/paused.t.sol index f6b65aa2b..a993da883 100644 --- a/packages/horizon/test/escrow/paused.t.sol +++ b/packages/horizon/test/escrow/paused.t.sol @@ -31,18 +31,18 @@ contract GraphEscrowPausedTest is GraphEscrowTest { function testPaused_RevertWhen_Deposit(uint256 tokens) public useGateway usePaused(true) { vm.expectRevert(abi.encodeWithSelector(IPaymentsEscrow.PaymentsEscrowIsPaused.selector)); - escrow.deposit(users.indexer, tokens); + escrow.deposit(users.verifier, users.indexer, tokens); } function testPaused_RevertWhen_DepositTo(uint256 tokens) public usePaused(true) { resetPrank(users.operator); vm.expectRevert(abi.encodeWithSelector(IPaymentsEscrow.PaymentsEscrowIsPaused.selector)); - escrow.depositTo(users.gateway, users.indexer, tokens); + escrow.depositTo(users.gateway, users.verifier, users.indexer, tokens); } function testPaused_RevertWhen_ThawTokens(uint256 tokens) public useGateway useDeposit(tokens) usePaused(true) { vm.expectRevert(abi.encodeWithSelector(IPaymentsEscrow.PaymentsEscrowIsPaused.selector)); - escrow.thaw(users.indexer, tokens); + escrow.thaw(users.verifier, users.indexer, tokens); } function testPaused_RevertWhen_WithdrawTokens( @@ -53,7 +53,7 @@ contract GraphEscrowPausedTest is GraphEscrowTest { skip(withdrawEscrowThawingPeriod + 1); vm.expectRevert(abi.encodeWithSelector(IPaymentsEscrow.PaymentsEscrowIsPaused.selector)); - escrow.withdraw(users.indexer); + escrow.withdraw(users.verifier, users.indexer); } // Collect diff --git a/packages/horizon/test/escrow/thaw.t.sol b/packages/horizon/test/escrow/thaw.t.sol index 36808602c..8e2674e00 100644 --- a/packages/horizon/test/escrow/thaw.t.sol +++ b/packages/horizon/test/escrow/thaw.t.sol @@ -16,10 +16,10 @@ contract GraphEscrowThawTest is GraphEscrowTest { function testThaw_Tokens(uint256 amount) public useGateway useDeposit(amount) { uint256 expectedThawEndTimestamp = block.timestamp + withdrawEscrowThawingPeriod; vm.expectEmit(address(escrow)); - emit IPaymentsEscrow.Thaw(users.gateway, users.indexer, amount, expectedThawEndTimestamp); - escrow.thaw(users.indexer, amount); + emit IPaymentsEscrow.Thaw(users.gateway, users.verifier, users.indexer, amount, expectedThawEndTimestamp); + escrow.thaw(users.verifier, users.indexer, amount); - (, uint256 amountThawing,uint256 thawEndTimestamp) = escrow.escrowAccounts(users.gateway, users.indexer); + (, uint256 amountThawing,uint256 thawEndTimestamp) = escrow.escrowAccounts(users.gateway, users.verifier, users.indexer); assertEq(amountThawing, amount); assertEq(thawEndTimestamp, expectedThawEndTimestamp); } @@ -29,7 +29,7 @@ contract GraphEscrowThawTest is GraphEscrowTest { ) public useGateway useDeposit(amount) { bytes memory expectedError = abi.encodeWithSignature("PaymentsEscrowNotThawing()"); vm.expectRevert(expectedError); - escrow.thaw(users.indexer, 0); + escrow.thaw(users.verifier, users.indexer, 0); } function testThaw_RevertWhen_InsufficientAmount( @@ -39,14 +39,14 @@ contract GraphEscrowThawTest is GraphEscrowTest { overAmount = bound(overAmount, amount + 1, type(uint256).max); bytes memory expectedError = abi.encodeWithSignature("PaymentsEscrowInsufficientBalance(uint256,uint256)", amount, overAmount); vm.expectRevert(expectedError); - escrow.thaw(users.indexer, overAmount); + escrow.thaw(users.verifier, users.indexer, overAmount); } function testThaw_CancelRequest(uint256 amount) public useGateway useDeposit(amount) { - escrow.thaw(users.indexer, amount); - escrow.thaw(users.indexer, 0); + escrow.thaw(users.verifier, users.indexer, amount); + escrow.thaw(users.verifier, users.indexer, 0); - (, uint256 amountThawing,uint256 thawEndTimestamp) = escrow.escrowAccounts(users.gateway, users.indexer); + (, uint256 amountThawing,uint256 thawEndTimestamp) = escrow.escrowAccounts(users.gateway, users.verifier, users.indexer); assertEq(amountThawing, 0); assertEq(thawEndTimestamp, 0); } diff --git a/packages/horizon/test/escrow/withdraw.t.sol b/packages/horizon/test/escrow/withdraw.t.sol index 0e5670c7a..a141d039b 100644 --- a/packages/horizon/test/escrow/withdraw.t.sol +++ b/packages/horizon/test/escrow/withdraw.t.sol @@ -18,17 +18,17 @@ contract GraphEscrowWithdrawTest is GraphEscrowTest { // advance time skip(withdrawEscrowThawingPeriod + 1); - escrow.withdraw(users.indexer); + escrow.withdraw(users.verifier, users.indexer); vm.stopPrank(); - (uint256 indexerEscrowBalance,,) = escrow.escrowAccounts(users.gateway, users.indexer); + (uint256 indexerEscrowBalance,,) = escrow.escrowAccounts(users.gateway, users.verifier, users.indexer); assertEq(indexerEscrowBalance, amount - thawAmount); } function testWithdraw_RevertWhen_NotThawing(uint256 amount) public useGateway useDeposit(amount) { bytes memory expectedError = abi.encodeWithSignature("PaymentsEscrowNotThawing()"); vm.expectRevert(expectedError); - escrow.withdraw(users.indexer); + escrow.withdraw(users.verifier, users.indexer); } function testWithdraw_RevertWhen_StillThawing( @@ -37,6 +37,6 @@ contract GraphEscrowWithdrawTest is GraphEscrowTest { ) public useGateway depositAndThawTokens(amount, thawAmount) { bytes memory expectedError = abi.encodeWithSignature("PaymentsEscrowStillThawing(uint256,uint256)", block.timestamp, block.timestamp + withdrawEscrowThawingPeriod); vm.expectRevert(expectedError); - escrow.withdraw(users.indexer); + escrow.withdraw(users.verifier, users.indexer); } } \ No newline at end of file diff --git a/packages/subgraph-service/test/subgraphService/collect/query/query.t.sol b/packages/subgraph-service/test/subgraphService/collect/query/query.t.sol index 6ad7b5347..f39e6537b 100644 --- a/packages/subgraph-service/test/subgraphService/collect/query/query.t.sol +++ b/packages/subgraph-service/test/subgraphService/collect/query/query.t.sol @@ -53,7 +53,7 @@ contract SubgraphServiceRegisterTest is SubgraphServiceTest { mint(signer, tokens); escrow.approveCollector(address(tapCollector), tokens); token.approve(address(escrow), tokens); - escrow.deposit(users.indexer, tokens); + escrow.deposit(address(tapCollector), users.indexer, tokens); resetPrank(msgSender); }