Skip to content

Commit

Permalink
Update EIP-6120: Update ERC-6120
Browse files Browse the repository at this point in the history
Merged by EIP-Bot.
  • Loading branch information
Zergity authored Aug 20, 2023
1 parent 739cb60 commit f4cac47
Showing 1 changed file with 88 additions and 16 deletions.
104 changes: 88 additions & 16 deletions EIPS/eip-6120.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
eip: 6120
title: Universal Token Router
description: A single router contract enables tokens to be sent to application contracts in the transfer-and-call pattern instead of approve-then-call.
author: Zergity (@Zergity), Ngo Quang Anh (@anhnq82), BerlinP (@BerlinP), Khanh Pham (@blackskin18)
author: Zergity (@Zergity), Ngo Quang Anh (@anhnq82), BerlinP (@BerlinP), Khanh Pham (@blackskin18), Hal Blackburn (@h4l)
discussions-to: https://ethereum-magicians.org/t/eip-6120-universal-token-router/12142
status: Review
type: Standards Track
Expand Down Expand Up @@ -119,9 +119,11 @@ struct Input {

`mode` takes one of the following values:

* `PAYMENT = 0`: the token can be transferred from `msg.sender` to the `recipient` by calling `UTR.pay` from anywhere in the same transaction.
* `TRANSFER = 1`: the token is transferred directly from `msg.sender` to `recipient`.
* `CALL_VALUE = 2`: the `ETH` amount will be passed to the action as the call `value`.
* `PAYMENT = 0`: pend a payment for the token to be transferred from `msg.sender` to the `recipient` by calling `UTR.pay` from anywhere in the same transaction.
* `TRANSFER = 1`: transfer the token directly from `msg.sender` to the `recipient`.
* `CALL_VALUE = 2`: record the `ETH` amount to pass to the action as the call `value`.

Each input in the `inputs` argument is processed sequentially. For simplicity, duplicated `PAYMENT` and `CALL_VALUE` inputs are valid, but only the last `amountIn` value is used.

#### Payment

Expand Down Expand Up @@ -274,6 +276,83 @@ UniversalTokenRouter.exec([{
}])
```

#### Allowance Adapter

A simple non-reentrancy ERC-20 adapter for aplication and router contracts that use direct allowance.

```solidity
contract AllowanceAdapter is ReentrancyGuard {
struct Input {
address token;
uint amountIn;
}
function approveAndCall(
Input[] memory inputs,
address spender,
bytes memory data,
address leftOverRecipient
) external payable nonReentrant {
for (uint i = 0; i < inputs.length; ++i) {
Input memory input = inputs[i];
IERC20(input.token).approve(spender, input.amountIn);
}
(bool success, bytes memory result) = spender.call{value: msg.value}(data);
if (!success) {
assembly {
revert(add(result, 32), mload(result))
}
}
for (uint i = 0; i < inputs.length; ++i) {
Input memory input = inputs[i];
// clear all allowance
IERC20(input.token).approve(spender, 0);
uint leftOver = IERC20(input.token).balanceOf(address(this));
if (leftOver > 0) {
TransferHelper.safeTransfer(input.token, leftOverRecipient, leftOver);
}
}
}
}
```

This transaction is constructed to utilize the `UTR` to interact with Uniswap V2 Router without approving any token to it:

```javascript
const { data: routerData } = await uniswapRouter.populateTransaction.swapExactTokensForTokens(
amountIn,
amountOutMin,
path,
to,
deadline,
)

const { data: adapterData } = await adapter.populateTransaction.approveAndCall(
[{
token: path[0],
amountIn,
}],
uniswapRouter.address,
routerData,
leftOverRecipient,
)

await utr.exec([], [{
inputs: [{
mode: TRANSFER,
recipient: adapter.address,
eip: 20,
token: path[0],
id: 0,
amountIn,
}],
code: adapter.address,
data: adapterData,
}])
```

## Rationale

The `Permit` type signature is not supported since the purpose of the Universal Token Router is to eliminate all interactive `approve` signatures for new tokens, and *most* for old tokens.
Expand Down Expand Up @@ -318,7 +397,7 @@ contract UniversalTokenRouter is IUniversalTokenRouter {
uint constant ERC_721_BALANCE = uint(keccak256('UniversalTokenRouter.ERC_721_BALANCE'));
// non-persistent in-transaction pending payments
// transient pending payments
mapping(bytes32 => uint) t_payments;
// accepting ETH for WETH.withdraw
Expand Down Expand Up @@ -352,7 +431,7 @@ contract UniversalTokenRouter is IUniversalTokenRouter {
} else if (mode == TRANSFER) {
_transferToken(sender, input.recipient, input.eip, input.token, input.id, input.amountIn);
} else if (mode == CALL_VALUE) {
// require(input.eip == EIP_ETH && input.id == 0, "UniversalTokenRouter: ETH_ONLY");
// eip and id are ignored
value = input.amountIn;
}
}
Expand All @@ -364,11 +443,11 @@ contract UniversalTokenRouter is IUniversalTokenRouter {
}
}
}
// clear all in-transaction storages, allowances and left-overs
// clear all transient storages, allowances and left-overs
for (uint j = 0; j < action.inputs.length; ++j) {
Input memory input = action.inputs[j];
if (input.mode == PAYMENT) {
// in-transaction storages
// transient storages
bytes32 key = keccak256(abi.encodePacked(sender, input.recipient, input.eip, input.token, input.id));
delete t_payments[key];
}
Expand Down Expand Up @@ -435,18 +514,11 @@ contract UniversalTokenRouter is IUniversalTokenRouter {
uint amount
) internal {
if (eip == 20) {
if (sender == address(this)) {
TransferHelper.safeTransfer(token, recipient, amount);
} else {
TransferHelper.safeTransferFrom(token, sender, recipient, amount);
}
TransferHelper.safeTransferFrom(token, sender, recipient, amount);
} else if (eip == 1155) {
IERC1155(token).safeTransferFrom(sender, recipient, id, amount, "");
} else if (eip == 721) {
IERC721(token).safeTransferFrom(sender, recipient, id);
} else if (eip == EIP_ETH) {
require(sender == address(this), 'UniversalTokenRouter: INVALID_ETH_SENDER');
TransferHelper.safeTransferETH(recipient, amount);
} else {
revert("UniversalTokenRouter: INVALID_EIP");
}
Expand Down

0 comments on commit f4cac47

Please sign in to comment.