Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions script/check_storage_layout.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ fi
PINNED_FACETS=(
InterestAdminFacet
QueueForecastFacet
RescueFacet
)

for name in "${PINNED_FACETS[@]}"; do
Expand Down
12 changes: 11 additions & 1 deletion script/diamond/WiseTelecomNodesDiamondSelectors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {QueueJoinLeaveFacet} from "../../src/diamond/vault/facets/QueueJoinLeave
import {QueueFulfillFacet} from "../../src/diamond/vault/facets/QueueFulfillFacet.sol";
import {QueueForecastFacet} from "../../src/diamond/vault/facets/QueueForecastFacet.sol";
import {InterestAdminFacet} from "../../src/diamond/vault/facets/InterestAdminFacet.sol";
import {RescueFacet} from "../../src/diamond/vault/facets/RescueFacet.sol";
import {WiseTelecomNodesQueueUIHelper} from "../../src/diamond/vault/helpers/WiseTelecomNodesQueueUIHelper.sol";
import {WiseTelecomNodesQueueHelper} from "../../src/diamond/vault/helpers/WiseTelecomNodesQueueHelper.sol";

Expand All @@ -30,7 +31,7 @@ import {WiseTelecomNodesQueueHelper} from "../../src/diamond/vault/helpers/WiseT
* queueAdmin=2, queueJoinLeave=5, queueFulfill=4, queueView=10 —
* total 90. Post-launch additions (registered via the timelocked
* selector proposals, not part of the genesis 90): queueForecast=1,
* interestAdmin=1.
* interestAdmin=1, rescue=1.
*/
library WiseTelecomNodesDiamondSelectors {

Expand Down Expand Up @@ -133,6 +134,15 @@ library WiseTelecomNodesDiamondSelectors {
sels[0] = InterestAdminFacet.setCashedInterest.selector;
}

function rescueSelectors()
internal
pure
returns (bytes4[] memory sels)
{
sels = new bytes4[](1);
sels[0] = RescueFacet.rescueToken.selector;
}

function burnWiseSelectors()
internal
pure
Expand Down
2 changes: 2 additions & 0 deletions src/diamond/vault/WiseTelecomNodesDiamondErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -147,4 +147,6 @@ abstract contract WiseTelecomNodesDiamondErrors {
error NegativeIncentiveNotAllowed();

error SameIncentive();

error ProtectedToken();
}
6 changes: 6 additions & 0 deletions src/diamond/vault/WiseTelecomNodesDiamondEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,12 @@ abstract contract WiseTelecomNodesDiamondEvents {
uint256 newAmount
);

event TokenRescued(
address indexed token,
address indexed to,
uint256 amount
);

event SweeperSet(
address indexed sweeper,
bool allowed
Expand Down
100 changes: 100 additions & 0 deletions src/diamond/vault/facets/RescueFacet.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: -- WISE --

pragma solidity =0.8.36;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import {WiseTelecomNodesDiamondErrors} from "../WiseTelecomNodesDiamondErrors.sol";
import {WiseTelecomNodesDiamondEvents} from "../WiseTelecomNodesDiamondEvents.sol";

import {NotMaster} from "../../shared/OwnableMaster.sol";
import {OnlyDelegateCall} from "../../shared/DiamondErrors.sol";

/**
* @dev Master-only escape hatch for tokens stranded on the vault by
* direct wallet transfers. Strictly excluded: the underlying
* `USD_TOKEN`, whose vault balance backs interest claims and the
* sweep-buffer reservation (its surplus leaves only through
* `sweepOverhang` to the worker), and the vault's own share token.
* Every other balance is invisible to vault accounting and can be
* returned to its sender.
*
* No reentrancy guard: the single external call happens after all
* checks, the function writes no vault storage, and the caller is
* the master.
*
* DEPLOY-SLIM STORAGE MIRROR: instead of inheriting the full
* declaration chain, only the two slots this facet reads are
* pinned, padded to their exact positions in the deployed diamond
* layout (master 0, USD_TOKEN 8). The pinned entries are asserted
* label-for-label against the diamond's committed layout snapshot
* by script/check_storage_layout.sh, so any drift fails CI before
* it can ship.
*/
contract RescueFacet is
WiseTelecomNodesDiamondErrors,
WiseTelecomNodesDiamondEvents
{
using SafeERC20 for IERC20;

address internal master;

uint256[7] private __gap1;

IERC20 internal USD_TOKEN;

address internal immutable _self;

constructor() {
_self = address(this);
}

modifier onlyDelegateCall() {
require(
address(this) != _self,
OnlyDelegateCall()
);
_;
}

modifier onlyMaster() {
require(
msg.sender == master,
NotMaster()
);
_;
}

function rescueToken(
address _token,
address _to,
uint256 _amount
)
external
onlyDelegateCall
onlyMaster
{
require(
_token != address(USD_TOKEN)
&& _token != address(this),
ProtectedToken()
);

require(
_to != address(0),
InvalidValue()
);

IERC20(_token).safeTransfer(
_to,
_amount
);

emit TokenRescued(
_token,
_to,
_amount
);
}
}
Loading
Loading