diff --git a/src/contracts/L1OpUSDCBridgeAdapter.sol b/src/contracts/L1OpUSDCBridgeAdapter.sol index 0bd80d28..979403f7 100644 --- a/src/contracts/L1OpUSDCBridgeAdapter.sol +++ b/src/contracts/L1OpUSDCBridgeAdapter.sol @@ -113,6 +113,9 @@ contract L1OpUSDCBridgeAdapter is IL1OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { if (messengerStatus != Status.Deprecated) revert IOpUSDCBridgeAdapter_BurnAmountNotSet(); // Burn the USDC tokens + // NOTE: If in flight transactions fail due to user being blacklisted after migration + // The funds will just be trapped in this contract as its deprecated + // If the user is after unblacklisted, they will be able to withdraw their usdc uint256 _burnAmount = burnAmount; if (_burnAmount != 0) { // NOTE: This is a very edge case and will only happen if the chain operator adds a second minter on L2 @@ -134,7 +137,7 @@ contract L1OpUSDCBridgeAdapter is IL1OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { } /*/////////////////////////////////////////////////////////////// - MESSAGING CONTROL + ADMIN CONTROL ///////////////////////////////////////////////////////////////*/ /** @@ -268,7 +271,38 @@ contract L1OpUSDCBridgeAdapter is IL1OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { */ function receiveMessage(address _user, uint256 _amount) external override onlyLinkedAdapter { // Transfer the tokens to the user + try this.attemptTransfer(_user, _amount) { + emit MessageReceived(_user, _amount, MESSENGER); + } catch { + userBlacklistedFunds[_user] += _amount; + emit MessageFailed(_user, _amount); + } + } + + /** + * @notice Withdraws the blacklisted funds from the contract incase they get unblacklisted + * @param _user The user to withdraw the funds for + */ + function withdrawBlacklistedFunds(address _user) external override { + uint256 _amount = userBlacklistedFunds[_user]; + userBlacklistedFunds[_user] = 0; + + // The check for if the user is blacklisted happens in USDC's contract IUSDC(USDC).safeTransfer(_user, _amount); - emit MessageReceived(_user, _amount, MESSENGER); + + emit BlacklistedFundsWithdrawn(_user, _amount); + } + + /** + * @notice Attempts to transfer the tokens to the user + * @param _to The target address on the destination chain + * @param _amount The amount of tokens to send + * @dev This function should only be called when receiving a message + * And is a workaround for the fact that try/catch + * Only works on external calls and SafeERC20 is an internal library + */ + function attemptTransfer(address _to, uint256 _amount) external { + if (msg.sender != address(this)) revert IOpUSDCBridgeAdapter_InvalidSender(); + IUSDC(USDC).safeTransfer(_to, _amount); } } diff --git a/src/contracts/L2OpUSDCBridgeAdapter.sol b/src/contracts/L2OpUSDCBridgeAdapter.sol index 565709bf..a3d07b6c 100644 --- a/src/contracts/L2OpUSDCBridgeAdapter.sol +++ b/src/contracts/L2OpUSDCBridgeAdapter.sol @@ -78,6 +78,9 @@ contract L2OpUSDCBridgeAdapter is IL2OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { isMessagingDisabled = true; roleCaller = _roleCaller; + // We need to do totalSupply + blacklistedFunds + // Because on `receiveMessage` mint would fail causing the totalSupply to not increase + // But the native token is still locked on L1 uint256 _burnAmount = IUSDC(USDC).totalSupply(); ICrossDomainMessenger(MESSENGER).sendMessage( @@ -216,8 +219,30 @@ contract L2OpUSDCBridgeAdapter is IL2OpUSDCBridgeAdapter, OpUSDCBridgeAdapter { */ function receiveMessage(address _user, uint256 _amount) external override onlyLinkedAdapter { // Mint the tokens to the user + try IUSDC(USDC).mint(_user, _amount) { + emit MessageReceived(_user, _amount, MESSENGER); + } catch { + userBlacklistedFunds[_user] += _amount; + emit MessageFailed(_user, _amount); + } + } + + /** + * @notice Mints the blacklisted funds from the contract incase they get unblacklisted + * @param _user The user to withdraw the funds for + */ + function withdrawBlacklistedFunds(address _user) external override { + uint256 _amount = userBlacklistedFunds[_user]; + userBlacklistedFunds[_user] = 0; + + // NOTE: This will fail after migration as the adapter will no longer be a minter + // All funds need to be recovered from the contract before migration if applicable + // TODO: If migration has happend instead send a message back to L1 to recover the funds + + // The check for if the user is blacklisted happens in USDC's contract IUSDC(USDC).mint(_user, _amount); - emit MessageReceived(_user, _amount, MESSENGER); + + emit BlacklistedFundsWithdrawn(_user, _amount); } /*/////////////////////////////////////////////////////////////// diff --git a/src/contracts/universal/OpUSDCBridgeAdapter.sol b/src/contracts/universal/OpUSDCBridgeAdapter.sol index d6540b93..7c10fc77 100644 --- a/src/contracts/universal/OpUSDCBridgeAdapter.sol +++ b/src/contracts/universal/OpUSDCBridgeAdapter.sol @@ -22,6 +22,9 @@ abstract contract OpUSDCBridgeAdapter is IOpUSDCBridgeAdapter, Ownable { /// @inheritdoc IOpUSDCBridgeAdapter mapping(address _user => mapping(uint256 _nonce => bool _used)) public userNonces; + /// @inheritdoc IOpUSDCBridgeAdapter + mapping(address _user => uint256 _blacklistedAmount) public userBlacklistedFunds; + /** * @notice Construct the OpUSDCBridgeAdapter contract * @param _usdc The address of the USDC Contract to be used by the adapter @@ -76,6 +79,12 @@ abstract contract OpUSDCBridgeAdapter is IOpUSDCBridgeAdapter, Ownable { */ function receiveMessage(address _user, uint256 _amount) external virtual; + /** + * @notice Withdraws the blacklisted funds from the contract if they get unblacklisted + * @param _user The user to withdraw the funds for + */ + function withdrawBlacklistedFunds(address _user) external virtual; + /** * @notice Cancels a signature by setting the nonce as used * @param _nonce The nonce of the signature to cancel diff --git a/src/interfaces/IOpUSDCBridgeAdapter.sol b/src/interfaces/IOpUSDCBridgeAdapter.sol index 5f57f352..44c0fc00 100644 --- a/src/interfaces/IOpUSDCBridgeAdapter.sol +++ b/src/interfaces/IOpUSDCBridgeAdapter.sol @@ -45,6 +45,20 @@ interface IOpUSDCBridgeAdapter { */ event MigratingToNative(address _messenger, address _caller); + /** + * @notice Emitted when a message fails + * @param _user The user that the message failed for + * @param _amount The amount of tokens that were added to the blacklisted funds + */ + event MessageFailed(address _user, uint256 _amount); + + /** + * @notice Emitted when the blacklisted funds are withdrawn + * @param _user The user that the funds were withdrawn for + * @param _amountWithdrawn The amount of tokens that were withdrawn + */ + event BlacklistedFundsWithdrawn(address _user, uint256 _amountWithdrawn); + /*/////////////////////////////////////////////////////////////// ERRORS ///////////////////////////////////////////////////////////////*/ @@ -152,6 +166,12 @@ interface IOpUSDCBridgeAdapter { */ function receiveMessage(address _user, uint256 _amount) external; + /** + * @notice Withdraws the blacklisted funds from the contract if they get unblacklisted + * @param _user The user to withdraw the funds for + */ + function withdrawBlacklistedFunds(address _user) external; + /** * @notice Cancels a signature by setting the nonce as used * @param _nonce The nonce of the signature to cancel @@ -190,4 +210,11 @@ interface IOpUSDCBridgeAdapter { * @return _used If the nonce has been used */ function userNonces(address _user, uint256 _nonce) external view returns (bool _used); + + /** + * @notice Returns the amount of funds locked that got blacklisted for a specific user + * @param _user The user to check for + * @return _amount The amount of funds locked from blacklisted messages + */ + function userBlacklistedFunds(address _user) external view returns (uint256 _amount); } diff --git a/src/interfaces/external/IUSDC.sol b/src/interfaces/external/IUSDC.sol index 7732404c..9a74847b 100644 --- a/src/interfaces/external/IUSDC.sol +++ b/src/interfaces/external/IUSDC.sol @@ -68,6 +68,18 @@ interface IUSDC is IERC20 { */ function updateMasterMinter(address _newMasterMinter) external; + /** + * @notice Adds account to blacklist + * @param _account The address to blacklist + */ + function blacklist(address _account) external; + + /** + * @notice Removes account from blacklist. + * @param _account The address to remove from the blacklist. + */ + function unBlacklist(address _account) external; + /** * @notice Function to upgrade the usdc proxy to a new implementation * @param _newImplementation Address of the new implementation diff --git a/test/integration/L1OpUSDCBridgeAdapter.t.sol b/test/integration/L1OpUSDCBridgeAdapter.t.sol index ee72f708..c20f21d9 100644 --- a/test/integration/L1OpUSDCBridgeAdapter.t.sol +++ b/test/integration/L1OpUSDCBridgeAdapter.t.sol @@ -296,4 +296,42 @@ contract Integration_Integration_PermissionedFlows is IntegrationBase { assertEq(l2Adapter.isMessagingDisabled(), false); } + + /** + * @notice Test that the user can withdraw the blacklisted funds if they get unblacklisted + */ + function test_userCanWithdrawBlacklistedFunds() public { + vm.selectFork(mainnet); + _mintSupplyOnL2(optimism, OP_ALIASED_L1_MESSENGER, _amount); + + vm.selectFork(optimism); + vm.startPrank(_user); + bridgedUSDC.approve(address(l2Adapter), _amount); + l2Adapter.sendMessage(_user, _amount, _MIN_GAS_LIMIT); + vm.stopPrank(); + assertEq(bridgedUSDC.balanceOf(_user), 0); + + vm.selectFork(mainnet); + + vm.prank(MAINNET_USDC.blacklister()); + MAINNET_USDC.blacklist(_user); + _relayL2ToL1Message( + address(l2Adapter), + address(l1Adapter), + _ZERO_VALUE, + _MIN_GAS_LIMIT, + abi.encodeWithSignature('receiveMessage(address,uint256)', _user, _amount) + ); + + assertEq(MAINNET_USDC.balanceOf(_user), 0); + + vm.prank(MAINNET_USDC.blacklister()); + MAINNET_USDC.unBlacklist(_user); + + vm.prank(_user); + l1Adapter.withdrawBlacklistedFunds(_user); + + assertEq(MAINNET_USDC.balanceOf(_user), _amount); + assertEq(l1Adapter.userBlacklistedFunds(_user), 0); + } } diff --git a/test/integration/L2OpUSDCBridgeAdapter.t.sol b/test/integration/L2OpUSDCBridgeAdapter.t.sol index c14f0276..3fad489f 100644 --- a/test/integration/L2OpUSDCBridgeAdapter.t.sol +++ b/test/integration/L2OpUSDCBridgeAdapter.t.sol @@ -88,6 +88,41 @@ contract Integration_Bridging is IntegrationBase { assertEq(MAINNET_USDC.balanceOf(address(l1Adapter)), 0); } + /** + * @notice Test bridging with a user who is blacklisted on L1 + */ + function test_bridgingWithBlacklistedUser() public { + vm.selectFork(mainnet); + vm.prank(MAINNET_USDC.blacklister()); + MAINNET_USDC.blacklist(_user); + + vm.selectFork(optimism); + // Mint to increment total supply of bridgedUSDC and balance of _user + vm.prank(address(l2Adapter)); + bridgedUSDC.mint(_user, _amount); + + vm.startPrank(_user); + bridgedUSDC.approve(address(l2Adapter), _amount); + l2Adapter.sendMessage(_user, _amount, _MIN_GAS_LIMIT); + vm.stopPrank(); + + assertEq(bridgedUSDC.balanceOf(_user), 0); + assertEq(bridgedUSDC.balanceOf(address(l2Adapter)), 0); + + vm.selectFork(mainnet); + _relayL2ToL1Message( + address(l2Adapter), + address(l1Adapter), + _ZERO_VALUE, + _MIN_GAS_LIMIT, + abi.encodeWithSignature('receiveMessage(address,uint256)', _user, _amount) + ); + + assertEq(MAINNET_USDC.balanceOf(_user), 0); + assertEq(MAINNET_USDC.balanceOf(address(l1Adapter)), _amount); + assertEq(l1Adapter.userBlacklistedFunds(_user), _amount); + } + /** * @notice Test bridging with signature */ diff --git a/test/unit/L1OpUSDCBridgeAdapter.t.sol b/test/unit/L1OpUSDCBridgeAdapter.t.sol index d56fc07b..b818b3f8 100644 --- a/test/unit/L1OpUSDCBridgeAdapter.t.sol +++ b/test/unit/L1OpUSDCBridgeAdapter.t.sol @@ -28,6 +28,10 @@ contract ForTestL1OpUSDCBridgeAdapter is L1OpUSDCBridgeAdapter { function forTest_setUserNonce(address _user, uint256 _nonce, bool _used) external { userNonces[_user][_nonce] = _used; } + + function forTest_setUserBlacklistedFunds(address _user, uint256 _amount) external { + userBlacklistedFunds[_user] = _amount; + } } abstract contract Base is Helpers { @@ -678,6 +682,7 @@ contract L1OpUSDCBridgeAdapter_Unit_ResumeMessaging is Base { /*/////////////////////////////////////////////////////////////// MESSAGING ///////////////////////////////////////////////////////////////*/ + contract L1OpUSDCBridgeAdapter_Unit_SendMessage is Base { /** * @notice Check that the function reverts if the address is blacklisted @@ -945,6 +950,8 @@ contract L1OpUSDCBridgeAdapter_Unit_SendMessageWithSignature is Base { } contract L1OpUSDCBridgeAdapter_Unit_ReceiveMessage is Base { + event MessageFailed(address _user, uint256 _amount); + /** * @notice Check that the function reverts if the sender is not the messenger */ @@ -999,4 +1006,138 @@ contract L1OpUSDCBridgeAdapter_Unit_ReceiveMessage is Base { vm.prank(_messenger); adapter.receiveMessage(_user, _amount); } + + /** + * @notice Check that blacklisted funds are incremented as expected on failure + */ + function test_incrementBlacklistedFundsOnTransferReturnFalse(uint256 _amount, address _user) external { + vm.assume(_amount > 0); + vm.assume(_user != address(0)); + + // Mock calls + vm.mockCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()'), abi.encode(_linkedAdapter)); + vm.mockCall(_usdc, abi.encodeWithSignature('transfer(address,uint256)', _user, _amount), abi.encode(false)); + + // Execute + vm.prank(_messenger); + adapter.receiveMessage(_user, _amount); + + assertEq(adapter.userBlacklistedFunds(_user), _amount, 'User blacklisted funds should be incremented'); + } + + /** + * @notice Check that blacklisted funds are incremented as expected on failure + */ + function test_incrementBlacklistedFundsOnTransferRevert(uint256 _amount, address _user) external { + vm.assume(_amount > 0); + vm.assume(_user != address(0)); + + // Mock calls + vm.mockCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()'), abi.encode(_linkedAdapter)); + vm.mockCall(_usdc, abi.encodeWithSignature('transfer(address,uint256)', _user, _amount), abi.encode(true)); + vm.mockCallRevert(_usdc, abi.encodeWithSignature('transfer(address,uint256)', _user, _amount), abi.encode()); + + // Execute + vm.prank(_messenger); + adapter.receiveMessage(_user, _amount); + + assertEq(adapter.userBlacklistedFunds(_user), _amount, 'User blacklisted funds should be incremented'); + } + + /** + * @notice Check that event is emitted as expected on failure + */ + function test_incrementBlacklistedFundsEmitsFailureEvent(uint256 _amount, address _user) external { + vm.assume(_amount > 0); + vm.assume(_user != address(0)); + + // Mock calls + vm.mockCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()'), abi.encode(_linkedAdapter)); + vm.mockCall(_usdc, abi.encodeWithSignature('transfer(address,uint256)', _user, _amount), abi.encode(true)); + vm.mockCallRevert(_usdc, abi.encodeWithSignature('transfer(address,uint256)', _user, _amount), abi.encode()); + + // Execute + vm.expectEmit(true, true, true, true); + vm.prank(_messenger); + emit MessageFailed(_user, _amount); + adapter.receiveMessage(_user, _amount); + } +} + +contract L1OpUSDCBridgeAdapter_Unit_WithdrawBlacklistedFunds is Base { + event BlacklistedFundsWithdrawn(address _user, uint256 _amountWithdrawn); + + /** + * @notice Check that the function expects the correct calls + */ + function test_expectedCalls(uint256 _amount, address _user) external { + vm.assume(_amount > 0); + vm.assume(_user != address(0)); + adapter.forTest_setUserBlacklistedFunds(_user, _amount); + + // Mock calls + _mockAndExpect(_usdc, abi.encodeWithSignature('transfer(address,uint256)', _user, _amount), abi.encode(true)); + + // Execute + vm.prank(_user); + adapter.withdrawBlacklistedFunds(_user); + } + + /** + * @notice Check that the updates the state as expected + */ + function test_updateState(uint256 _amount, address _user) external { + vm.assume(_amount > 0); + vm.assume(_user != address(0)); + adapter.forTest_setUserBlacklistedFunds(_user, _amount); + + // Mock calls + vm.mockCall(_usdc, abi.encodeWithSignature('transfer(address,uint256)', _user, _amount), abi.encode(true)); + + // Execute + vm.prank(_user); + adapter.withdrawBlacklistedFunds(_user); + + assertEq(adapter.userBlacklistedFunds(_user), 0, 'User blacklisted funds should be updated'); + } + + function test_emitsEvent(uint256 _amount, address _user) external { + vm.assume(_amount > 0); + vm.assume(_user != address(0)); + adapter.forTest_setUserBlacklistedFunds(_user, _amount); + + // Mock calls + vm.mockCall(_usdc, abi.encodeWithSignature('transfer(address,uint256)', _user, _amount), abi.encode(true)); + + // Expect events + vm.expectEmit(true, true, true, true); + emit BlacklistedFundsWithdrawn(_user, _amount); + + // Execute + vm.prank(_user); + adapter.withdrawBlacklistedFunds(_user); + } +} + +contract L1OpUSDCBridgeAdapter_Unit_AttemptTransfer is Base { + /** + * @notice Check that the function reverts if the sender is not the contract + */ + function test_onlySelf(address _notSelf, uint256 _amount) external { + vm.assume(_notSelf != address(adapter)); + // Execute + vm.prank(_notSelf); + vm.expectRevert(IOpUSDCBridgeAdapter.IOpUSDCBridgeAdapter_InvalidSender.selector); + adapter.attemptTransfer(_user, _amount); + } + + /** + * @notice Check that the function transfers the tokens + */ + function test_transfer(uint256 _amount) external { + _mockAndExpect(_usdc, abi.encodeWithSignature('transfer(address,uint256)', _user, _amount), abi.encode()); + + vm.prank(address(adapter)); + adapter.attemptTransfer(_user, _amount); + } } diff --git a/test/unit/L2OpUSDCBridgeAdapter.t.sol b/test/unit/L2OpUSDCBridgeAdapter.t.sol index 4a4ea629..fc06bae9 100644 --- a/test/unit/L2OpUSDCBridgeAdapter.t.sol +++ b/test/unit/L2OpUSDCBridgeAdapter.t.sol @@ -27,6 +27,10 @@ contract ForTestL2OpUSDCBridgeAdapter is L2OpUSDCBridgeAdapter { function forTest_setUserNonce(address _user, uint256 _nonce, bool _used) external { userNonces[_user][_nonce] = _used; } + + function forTest_setUserBlacklistedFunds(address _user, uint256 _amount) external { + userBlacklistedFunds[_user] = _amount; + } } abstract contract Base is Helpers { @@ -588,6 +592,8 @@ contract L2OpUSDCBridgeAdapter_Unit_SendMessageWithSignature is Base { } contract L2OpUSDCBridgeAdapter_Unit_ReceiveMessage is Base { + event MessageFailed(address _user, uint256 _amount); + /** * @notice Check that the function reverts if the sender is not the messenger */ @@ -643,6 +649,100 @@ contract L2OpUSDCBridgeAdapter_Unit_ReceiveMessage is Base { vm.prank(_messenger); adapter.receiveMessage(_user, _amount); } + + /** + * @notice Check that blacklisted funds are updated as expected + */ + function test_updateBlacklistedFunds(uint256 _amount) external { + vm.assume(_amount > 0); + vm.mockCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()'), abi.encode(_linkedAdapter)); + + // Need to mock call, then mock the revert (foundry bug?) + + vm.mockCall(_usdc, abi.encodeWithSignature('mint(address,uint256)', _user, _amount), abi.encode(true)); + vm.mockCallRevert(_usdc, abi.encodeWithSignature('mint(address,uint256)', _user, _amount), abi.encode(false)); + // Execute + vm.prank(_messenger); + adapter.receiveMessage(_user, _amount); + + assertEq(adapter.userBlacklistedFunds(_user), _amount, 'Blacklisted funds should be set to the amount'); + } + + /** + * @notice Check that the event is emitted as expected + */ + function test_emitEventFail(uint256 _amount) external { + vm.assume(_amount > 0); + + // Mock calls + vm.mockCall(_messenger, abi.encodeWithSignature('xDomainMessageSender()'), abi.encode(_linkedAdapter)); + + // Need to mock call, then mock the revert (foundry bug?) + vm.mockCall(_usdc, abi.encodeWithSignature('mint(address,uint256)', _user, _amount), abi.encode(true)); + vm.mockCallRevert(_usdc, abi.encodeWithSignature('mint(address,uint256)', _user, _amount), abi.encode(false)); + + // Execute + vm.expectEmit(true, true, true, true); + emit MessageFailed(_user, _amount); + + vm.prank(_messenger); + adapter.receiveMessage(_user, _amount); + } +} + +contract L2OpUSDCBridgeAdapter_Unit_WithdrawBlacklistedFunds is Base { + event BlacklistedFundsWithdrawn(address _user, uint256 _amountWithdrawn); + /** + * @notice Check that the function expects the correct calls + */ + + function test_expectedCalls(uint256 _amount, address _user) external { + vm.assume(_amount > 0); + vm.assume(_user != address(0)); + adapter.forTest_setUserBlacklistedFunds(_user, _amount); + + // Mock calls + _mockAndExpect(_usdc, abi.encodeWithSignature('mint(address,uint256)', _user, _amount), abi.encode(true)); + + // Execute + vm.prank(_user); + adapter.withdrawBlacklistedFunds(_user); + } + + /** + * @notice Check that the updates the state as expected + */ + function test_updateState(uint256 _amount, address _user) external { + vm.assume(_amount > 0); + vm.assume(_user != address(0)); + adapter.forTest_setUserBlacklistedFunds(_user, _amount); + + // Mock calls + vm.mockCall(_usdc, abi.encodeWithSignature('mint(address,uint256)', _user, _amount), abi.encode(true)); + + // Execute + vm.prank(_user); + adapter.withdrawBlacklistedFunds(_user); + + assertEq(adapter.userBlacklistedFunds(_user), 0, 'User blacklisted funds should be updated'); + } + + function test_emitsEvent(uint256 _amount, address _user) external { + vm.assume(_amount > 0); + vm.assume(_user != address(0)); + adapter.forTest_setUserBlacklistedFunds(_user, _amount); + + // Mock calls + vm.mockCall(_usdc, abi.encodeWithSignature('mint(address,uint256)', _user, _amount), abi.encode(true)); + + // Expect events + vm.expectEmit(true, true, true, true); + emit BlacklistedFundsWithdrawn(_user, _amount); + + // Execute + vm.prank(_user); + adapter.withdrawBlacklistedFunds(_user); + } } /*/////////////////////////////////////////////////////////////// diff --git a/test/unit/OpUSDCBridgeAdapter.t.sol b/test/unit/OpUSDCBridgeAdapter.t.sol index 475c1100..2b3d8f33 100644 --- a/test/unit/OpUSDCBridgeAdapter.t.sol +++ b/test/unit/OpUSDCBridgeAdapter.t.sol @@ -18,6 +18,8 @@ contract ForTestOpUSDCBridgeAdapter is OpUSDCBridgeAdapter { function sendMessage(address _to, uint256 _amount, uint32 _minGasLimit) external override {} + function withdrawBlacklistedFunds(address _user) external override {} + function sendMessage( address _signer, address _to,