Skip to content
Draft
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
24 changes: 24 additions & 0 deletions contracts/libraries/SafeSendLib.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/**
* @title SafeSendLib
* @dev Library for safely sending Ether to a recipient address.
*/
library SafeSendLib {
/**
* @dev Sends `amount` wei to address `to` in constructor by calling selfdestruct.
* Even recipients that are contracts with revert in fallback() or receive() will receive the funds.
* @param to The address to send Ether to.
* @param amount The amount of wei to send.
*/
function safeSend(address to, uint256 amount) internal {
assembly ("memory-safe") { // solhint-disable-line no-inline-assembly
mstore(0, to)
mstore8(11, 0x73) // 0x73 = PUSH20 opcode
mstore8(32, 0xff) // 0xff = SELFDESTRUCT opcode
pop(create(amount, 11, 22))
}
}
}
57 changes: 57 additions & 0 deletions contracts/libraries/UniERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import "../interfaces/IERC20MetadataUppercase.sol";
import "./SafeERC20.sol";
import "./StringUtil.sol";
import "./SafeSendLib.sol";

/**
* @title UniERC20
Expand All @@ -15,6 +16,7 @@ import "./StringUtil.sol";
*/
library UniERC20 {
using SafeERC20 for IERC20;
using SafeSendLib for address payable;

error InsufficientBalance();
error ApproveCalledOnETH();
Expand Down Expand Up @@ -73,6 +75,29 @@ library UniERC20 {
}
}

/**
* @dev Transfers a specified amount of the token to a given address.
* Note: Does nothing if the amount is zero.
* Note 2: Uses the SafeSendLib for safe Ether transfers.
* @param token The token to transfer.
* @param to The address to transfer the token to.
* @param amount The amount of the token to transfer.
*/
function uniSafeTransfer(
IERC20 token,
address payable to,
uint256 amount
) internal {
if (amount > 0) {
if (isETH(token)) {
if (address(this).balance < amount) revert InsufficientBalance();
to.safeSend(amount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about check address(this).code.length == 0 that code is executing inside the constructor?

} else {
token.safeTransfer(to, amount);
}
}
}

/**
* @dev Transfers a specified amount of the token from one address to another.
* Note: Does nothing if the amount is zero.
Expand Down Expand Up @@ -106,6 +131,38 @@ library UniERC20 {
}
}

/**
* @dev Transfers a specified amount of the token from one address to another.
* Note: Does nothing if the amount is zero.
* Note 2: Uses the SafeSendLib for safe Ether transfers.
* @param token The token to transfer.
* @param from The address to transfer the token from.
* @param to The address to transfer the token to.
* @param amount The amount of the token to transfer.
*/
function uniSafeTransferFrom(
IERC20 token,
address payable from,
address to,
uint256 amount
) internal {
if (amount > 0) {
if (isETH(token)) {
if (msg.value < amount) revert NotEnoughValue();
if (from != msg.sender) revert FromIsNotSender();
if (to != address(this)) revert ToIsNotThis();
if (msg.value > amount) {
// Return remainder if exist
unchecked {
from.safeSend(msg.value - amount);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The method sends ETH from msg.sender to somewhere vie eip7702? It's not obvious where the funds will go despite that the description is they have to go to the to

}
}
} else {
token.safeTransferFrom(from, to, amount);
}
}
}

/**
* @dev Retrieves the symbol from ERC20 metadata of the specified token.
* @param token The token to retrieve the symbol of.
Expand Down
22 changes: 22 additions & 0 deletions contracts/tests/mocks/UniERC20Helper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ interface IUniERC20Wrapper {
address to,
uint256 amount
) external payable;

function safeTransferFrom(
address payable from,
address to,
uint256 amount
) external payable;
}

contract UniERC20Wrapper {
Expand All @@ -27,6 +33,10 @@ contract UniERC20Wrapper {
_token.uniTransfer(to, amount);
}

function safeTransfer(address payable to, uint256 amount) external payable {
_token.uniSafeTransfer(to, amount);
}

function transferFrom(
address payable from,
address to,
Expand All @@ -35,6 +45,14 @@ contract UniERC20Wrapper {
_token.uniTransferFrom(from, to, amount);
}

function safeTransferFrom(
address payable from,
address to,
uint256 amount
) external payable {
_token.uniSafeTransferFrom(from, to, amount);
}

function approve(address spender, uint256 amount) external {
_token.uniApprove(spender, amount);
}
Expand Down Expand Up @@ -73,6 +91,10 @@ contract ETHBadReceiver {
_wrapper.transferFrom{value: msg.value}(payable(address(this)), to, amount);
}

function safeTransfer(address to, uint256 amount) external payable {
_wrapper.safeTransferFrom{value: msg.value}(payable(address(this)), to, amount);
}

receive() external payable {
revert ReceiveFailed();
}
Expand Down
31 changes: 31 additions & 0 deletions test/contracts/UniERC20.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ describe('UniERC20', function () {
expect(await wrapper.balanceOf(signer2)).to.be.equal(100);
});

it('uni safe transfer', async function () {
const { wrapper, token } = await loadFixture(deployMocks);
await token.transfer(wrapper, 100);
await wrapper.safeTransfer(signer2, 100);
expect(await wrapper.balanceOf(signer2)).to.be.equal(100);
});

it('uni transfer from', async function () {
const { wrapper, token } = await loadFixture(deployMocks);
await token.transfer(signer2, 100);
Expand All @@ -46,6 +53,14 @@ describe('UniERC20', function () {
expect(await wrapper.balanceOf(signer3)).to.be.equal(100);
});

it('uni safe transfer from', async function () {
const { wrapper, token } = await loadFixture(deployMocks);
await token.transfer(signer2, 100);
await token.connect(signer2).approve(wrapper, 100);
await wrapper.safeTransferFrom(signer2, signer3, 100);
expect(await wrapper.balanceOf(signer3)).to.be.equal(100);
});

it('uni approve', async function () {
const { wrapper, token } = await loadFixture(deployMocks);
await token.transfer(wrapper, 100);
Expand Down Expand Up @@ -248,6 +263,22 @@ describe('UniERC20', function () {
}),
).to.eventually.be.rejectedWith('ETHTransferFailed');
});

it('uni safe transfer, success', async function () {
const { wrapper, receiver } = await loadFixture(deployMocks);
const balBefore = await wrapper.balanceOf(receiver);
await wrapper.safeTransfer(receiver, 100, { value: 100 });
const balAfter = await wrapper.balanceOf(receiver);
expect(balAfter - balBefore).to.be.equal(100);
});

it('uni safe transferFrom, success', async function () {
const { wrapper, receiver } = await loadFixture(deployMocks);
const balBefore = await wrapper.balanceOf(wrapper);
await receiver.safeTransfer(wrapper, 100, { value: 101n });
const balAfter = await wrapper.balanceOf(wrapper);
expect(balAfter - balBefore).to.be.equal(100);
});
});

describe('ETH from special address', function () {
Expand Down
Loading