Skip to content

Commit

Permalink
adds WithdrawablePeriphery base contract for token withdrawals (#831)
Browse files Browse the repository at this point in the history
* adds WithdrawablePeriphery base contract for token withdrawals

* fix test

* adds version tag to contract

* audit report added
  • Loading branch information
0xDEnYO authored Oct 15, 2024
1 parent 0dda420 commit a8da0f8
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 0 deletions.
10 changes: 10 additions & 0 deletions audit/auditLog.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
"auditorGitHandle": "sujithsomraaj",
"auditReportPath": "./audit/reports/2024.09.13_EmergencyPauseFacet.pdf",
"auditCommitHash": "77441a088e0789513db4e068f7ef6c5c0988ee42"
},
"audit20241014": {
"auditCompletedOn": "14.10.2024",
"auditedBy": "Sujith Somraaj (individual security researcher)",
"auditorGitHandle": "sujithsomraaj",
"auditReportPath": "./audit/reports/2024.10.14_WithdrawablePeriphery.pdf",
"auditCommitHash": "b03e658b359f1696388e2aaeaf24a7c107ba462a"
}
},
"auditedContracts": {
Expand All @@ -21,6 +28,9 @@
},
"StargateFacetV2": {
"1.0.1": ["audit20240814"]
},
"WithdrawablePeriphery": {
"1.0.0": ["audit20241014"]
}
}
}
Binary file not shown.
36 changes: 36 additions & 0 deletions src/Helpers/WithdrawablePeriphery.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity 0.8.17;

import { TransferrableOwnership } from "./TransferrableOwnership.sol";
import { LibAsset } from "../Libraries/LibAsset.sol";
import { ExternalCallFailed } from "../Errors/GenericErrors.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";

abstract contract WithdrawablePeriphery is TransferrableOwnership {
using SafeTransferLib for address;

event TokensWithdrawn(
address assetId,
address payable receiver,
uint256 amount
);

constructor(address _owner) TransferrableOwnership(_owner) {}

function withdrawToken(
address assetId,
address payable receiver,
uint256 amount
) external onlyOwner {
if (LibAsset.isNativeAsset(assetId)) {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = receiver.call{ value: amount }("");
if (!success) revert ExternalCallFailed();
} else {
assetId.safeTransfer(receiver, amount);
}

emit TokensWithdrawn(assetId, receiver, amount);
}
}
119 changes: 119 additions & 0 deletions test/solidity/Helpers/WithdrawablePeriphery.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: MIT
/// @custom:version 1.0.0
pragma solidity 0.8.17;

import { WithdrawablePeriphery } from "lifi/Helpers/WithdrawablePeriphery.sol";

import { TestBase } from "../utils/TestBase.sol";
import { NonETHReceiver } from "../utils/TestHelpers.sol";

contract TestContract is WithdrawablePeriphery {
constructor(address _owner) WithdrawablePeriphery(_owner) {}
}

contract WithdrawablePeripheryTest is TestBase {
WithdrawablePeriphery internal withdrawable;

event TokensWithdrawn(
address assetId,
address payable receiver,
uint256 amount
);

error UnAuthorized();

function setUp() public {
initTestBase();

// deploy contract
withdrawable = new TestContract(USER_DIAMOND_OWNER);

// fund contract with native and ERC20
deal(
ADDRESS_USDC,
address(withdrawable),
100_000 * 10 ** usdc.decimals()
);
deal(address(withdrawable), 1 ether);
}

function test_AllowsOwnerToWithdrawNative() public {
uint256 withdrawAmount = 0.1 ether;

vm.startPrank(USER_DIAMOND_OWNER);

vm.expectEmit(true, true, true, true, address(withdrawable));
emit TokensWithdrawn(
address(0),
payable(USER_RECEIVER),
withdrawAmount
);

withdrawable.withdrawToken(
address(0),
payable(USER_RECEIVER),
withdrawAmount
);
}

function test_AllowsOwnerToWithdrawERC20() public {
uint256 withdrawAmount = 10 * 10 ** usdc.decimals();
vm.startPrank(USER_DIAMOND_OWNER);

vm.expectEmit(true, true, true, true, address(withdrawable));
emit TokensWithdrawn(
ADDRESS_USDC,
payable(USER_RECEIVER),
withdrawAmount
);

withdrawable.withdrawToken(
ADDRESS_USDC,
payable(USER_RECEIVER),
withdrawAmount
);
}

function testRevert_FailsIfNonOwnerTriesToWithdrawNative() public {
uint256 withdrawAmount = 0.1 ether;

vm.startPrank(USER_SENDER);

vm.expectRevert(UnAuthorized.selector);

withdrawable.withdrawToken(
address(0),
payable(USER_RECEIVER),
withdrawAmount
);
}

function testRevert_FailsIfNonOwnerTriesToWithdrawERC20() public {
uint256 withdrawAmount = 10 * 10 ** usdc.decimals();
vm.startPrank(USER_SENDER);

vm.expectRevert(UnAuthorized.selector);

withdrawable.withdrawToken(
ADDRESS_USDC,
payable(USER_RECEIVER),
withdrawAmount
);
}

function testRevert_FailsIfNativeTokenTransferFails() public {
uint256 withdrawAmount = 0.1 ether;

address nonETHReceiver = address(new NonETHReceiver());

vm.startPrank(USER_DIAMOND_OWNER);

vm.expectRevert();

withdrawable.withdrawToken(
address(0),
payable(nonETHReceiver),
withdrawAmount
);
}
}

0 comments on commit a8da0f8

Please sign in to comment.