Skip to content

Commit

Permalink
✨ V1.1: feat: add optional minimum output amount in command
Browse files Browse the repository at this point in the history
  • Loading branch information
z0r0z authored Feb 21, 2024
2 parents c781389 + cc8baf8 commit 0b0749c
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 50 deletions.
20 changes: 10 additions & 10 deletions .gas-snapshot
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ IETest:testBalanceInETH() (gas: 40280)
IETest:testCommandSendERC0() (gas: 102922)
IETest:testCommandSendETH() (gas: 69404)
IETest:testCommandSendUSDC() (gas: 135652)
IETest:testCommandSwapDAI() (gas: 110448)
IETest:testCommandSwapETH() (gas: 112553)
IETest:testCommandSwapForETH() (gas: 130095)
IETest:testCommandSwapUSDC() (gas: 157137)
IETest:testCommandSwapUSDCForWBTC() (gas: 165026)
IETest:testDeploy() (gas: 2657924)
IETest:testENSNameOwnership() (gas: 83907)
IETest:testCommandSwapDAI() (gas: 105596)
IETest:testCommandSwapETH() (gas: 119738)
IETest:testCommandSwapForETH() (gas: 124044)
IETest:testCommandSwapUSDC() (gas: 165660)
IETest:testCommandSwapUSDCForWBTC() (gas: 166523)
IETest:testDeploy() (gas: 2842077)
IETest:testENSNameOwnership() (gas: 83841)
IETest:testIENameSetting() (gas: 8142)
IETest:testPreviewCommandSendDecimals() (gas: 91866)
IETest:testPreviewCommandSendUSDC() (gas: 66043)
IETest:testPreviewCommandSendDecimals() (gas: 91938)
IETest:testPreviewCommandSendUSDC() (gas: 66075)
IETest:testPreviewSend() (gas: 42620)
IETest:testPreviewSendCommand() (gas: 54501)
IETest:testPreviewSendCommand() (gas: 54533)
IETest:testSendETH() (gas: 59642)
IETest:testTotalSupply() (gas: 14808)
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ From natural language:

IE should deterministically and transparently operate to provide these utilities in an uncensorable medium like a Solidity smart contract.

[V1](./src/IE.sol) is a POC of this. [*Short demo and explainer thread on X.*](https://x.com/z0r0zzz/status/1758392014737920209?s=20)
[`V1`](./src/IE.sol) is a POC of this. [*Short demo and explainer thread on X.*](https://x.com/z0r0zzz/status/1758392014737920209?s=20)

## Command Syntax (⌘)

IE is approaching things from first-principles and a "show" rather than "tell" approach. There will be some experimentation.

Some things in V1 are likely very underoptimized for this particular use case.
Some things in `V1` are likely very underoptimized for this particular use case.

The bigger project is to identify syntax and phrasing for common types of onchain transactions in English to start. The following are identified as categories and phrases that should demonstrate this for many if not most natural language commands to generate txs.

Expand Down Expand Up @@ -58,11 +58,22 @@ aliases: *transfer*

aliases: *exchange*

#### < A >
*Words: 5*

**[action] [value] [asset] [to/for] [object]**
> *swap 1 ETH to/for DAI*
#### < B >
*Words: 6*

[action] [value] [asset] [to/for] [minOutputAmount] [object]
- **swap 1 ETH to/for 2500 DAI**

aliases: *exchange*

Note: In `V1.1` on Arbitrum, a `minOutputAmount` can be specified for swaps. It ensures that you receive a minimum output amount of `object` at the end of the swap, otherwise the transaction will revert. The default value is set to `0`.

------------------------------------

Phrases are provided in the order in which they are most expected. They are "naturalized" to lower case. The IE contract automatically does this, but front-ends should nonetheless try and format as close as possible (*i.e.*, through a simple LLM trained or prompted on these examples below).
Expand Down
110 changes: 78 additions & 32 deletions src/IE.sol
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ contract IE {
bytes signature;
}

/// @dev The `swap` command details.
struct SwapDetails {
address tokenIn;
address tokenOut;
uint256 amountIn;
bool ETHIn;
bool ETHOut;
}

/// =========================== ENUMS =========================== ///

/// @dev `ENSAsciiNormalizer` rules.
Expand Down Expand Up @@ -170,6 +179,7 @@ contract IE {
returns (
address to, // Receiver address.
uint256 amount, // Formatted amount.
uint256 minAmountOut, // Formatted amount.
address token, // Asset to send `to`.
bytes memory callData, // Raw calldata for send transaction.
bytes memory executeCallData // Anticipates common execute API.
Expand All @@ -182,9 +192,14 @@ contract IE {
_extractSend(normalized);
(to, amount, token, callData, executeCallData) = previewSend(_to, _amount, _token);
} else if (action == "swap" || action == "exchange") {
(string memory amountIn, string memory tokenIn, string memory tokenOut) =
_extractSwap(normalized);
(amount, token, to) = previewSwap(amountIn, tokenIn, tokenOut);
(
string memory amountIn,
string memory amountOutMinimum,
string memory tokenIn,
string memory tokenOut
) = _extractSwap(normalized);
(amount, minAmountOut, token, to) =
previewSwap(amountIn, amountOutMinimum, tokenIn, tokenOut);
} else {
revert InvalidSyntax(); // Invalid command format.
}
Expand Down Expand Up @@ -214,17 +229,24 @@ contract IE {
}

/// @dev Previews a `swap` command from the parts of a matched intent string.
function previewSwap(string memory amountIn, string memory tokenIn, string memory tokenOut)
function previewSwap(
string memory amountIn,
string memory amountOutMinimum,
string memory tokenIn,
string memory tokenOut
)
public
view
virtual
returns (uint256 _amountIn, address _tokenIn, address _tokenOut)
returns (uint256 _amountIn, uint256 _amountOut, address _tokenIn, address _tokenOut)
{
_tokenIn = _returnTokenConstant(bytes32(bytes(tokenIn)));
if (_tokenIn == address(0)) _tokenIn = tokens[tokenIn];
_tokenOut = _returnTokenConstant(bytes32(bytes(tokenOut)));
if (_tokenOut == address(0)) _tokenOut = tokens[tokenOut];
_amountIn = _stringToUint(amountIn, _tokenIn == ETH ? 18 : _tokenIn.readDecimals());
_amountOut =
_stringToUint(amountOutMinimum, _tokenOut == ETH ? 18 : _tokenOut.readDecimals());
}

/// @dev Checks ERC4337 userOp against the output of the command intent.
Expand All @@ -234,7 +256,7 @@ contract IE {
virtual
returns (bool)
{
(,,,, bytes memory executeCallData) = previewCommand(intent);
(,,,,, bytes memory executeCallData) = previewCommand(intent);
if (executeCallData.length != userOp.callData.length) return false;
return keccak256(executeCallData) == keccak256(userOp.callData);
}
Expand All @@ -246,7 +268,7 @@ contract IE {
virtual
returns (bool)
{
(,,,, bytes memory executeCallData) = previewCommand(intent);
(,,,,, bytes memory executeCallData) = previewCommand(intent);
if (executeCallData.length != userOp.callData.length) return false;
return keccak256(executeCallData) == keccak256(userOp.callData);
}
Expand All @@ -272,9 +294,13 @@ contract IE {
(string memory to, string memory amount, string memory token) = _extractSend(normalized);
send(to, amount, token);
} else if (action == "swap" || action == "exchange") {
(string memory amountIn, string memory tokenIn, string memory tokenOut) =
_extractSwap(normalized);
swap(amountIn, tokenIn, tokenOut);
(
string memory amountIn,
string memory amountOutMinimum,
string memory tokenIn,
string memory tokenOut
) = _extractSwap(normalized);
swap(amountIn, amountOutMinimum, tokenIn, tokenOut);
} else {
revert InvalidSyntax(); // Invalid command format.
}
Expand All @@ -297,28 +323,42 @@ contract IE {
}

/// @dev Executes a `swap` command from the parts of a matched intent string.
function swap(string memory amountIn, string memory tokenIn, string memory tokenOut)
public
payable
virtual
{
address _tokenIn = _returnTokenConstant(bytes32(bytes(tokenIn)));
if (_tokenIn == address(0)) _tokenIn = tokens[tokenIn];
address _tokenOut = _returnTokenConstant(bytes32(bytes(tokenOut)));
if (_tokenOut == address(0)) _tokenOut = tokens[tokenOut];
bool ETHIn = _tokenIn == ETH;
bool ETHOut = _tokenOut == ETH;
if (ETHIn) _tokenIn = WETH;
if (ETHOut) _tokenOut = WETH;
uint256 _amountIn = _stringToUint(amountIn, ETHIn ? 18 : _tokenIn.readDecimals());
if (_amountIn >= 1 << 255) revert Overflow();
(address pool, bool zeroForOne) = _computePoolAddress(_tokenIn, _tokenOut);
ISwapRouter(pool).swap(
!ETHOut ? msg.sender : address(this),
function swap(
string memory amountIn,
string memory amountOutMinimum,
string memory tokenIn,
string memory tokenOut
) public payable virtual {
SwapDetails memory details;
details.tokenIn = _returnTokenConstant(bytes32(bytes(tokenIn)));
if (details.tokenIn == address(0)) details.tokenIn = tokens[tokenIn];
details.tokenOut = _returnTokenConstant(bytes32(bytes(tokenOut)));
if (details.tokenOut == address(0)) details.tokenOut = tokens[tokenOut];

details.ETHIn = details.tokenIn == ETH;
if (details.ETHIn) details.tokenIn = WETH;
details.ETHOut = details.tokenOut == ETH;
if (details.ETHOut) details.tokenOut = WETH;

details.amountIn =
_stringToUint(amountIn, details.ETHIn ? 18 : details.tokenIn.readDecimals());
if (details.amountIn >= 1 << 255) revert Overflow();
(address pool, bool zeroForOne) = _computePoolAddress(details.tokenIn, details.tokenOut);
(int256 amount0, int256 amount1) = ISwapRouter(pool).swap(
!details.ETHOut ? msg.sender : address(this),
zeroForOne,
int256(_amountIn),
int256(details.amountIn),
zeroForOne ? MIN_SQRT_RATIO_PLUS_ONE : MAX_SQRT_RATIO_MINUS_ONE,
abi.encodePacked(ETHIn, ETHOut, msg.sender, _tokenIn, _tokenOut)
abi.encodePacked(
details.ETHIn, details.ETHOut, msg.sender, details.tokenIn, details.tokenOut
)
);
require(
uint256(-(zeroForOne ? amount1 : amount0))
>= _stringToUint(
amountOutMinimum, details.ETHOut ? 18 : details.tokenOut.readDecimals()
),
"Too little received"
);
}

Expand Down Expand Up @@ -594,10 +634,16 @@ contract IE {
internal
pure
virtual
returns (string memory amountIn, string memory tokenIn, string memory tokenOut)
returns (
string memory amountIn,
string memory amountOutMinimum,
string memory tokenIn,
string memory tokenOut
)
{
string[] memory parts = _split(normalizedIntent, " ");
if (parts.length == 5) return (parts[1], parts[2], parts[4]);
if (parts.length == 5) return (parts[1], "", parts[2], parts[4]);
if (parts.length == 6) return (parts[1], parts[4], parts[2], parts[5]);
else revert InvalidSyntax(); // Command is not formatted.
}

Expand Down
12 changes: 6 additions & 6 deletions test/IE.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ contract IETest is Test {

function testPreviewSendCommand() public payable {
string memory command = "send vitalik 20 dai";
(address to, uint256 amount, address asset,,) = ie.previewCommand(command);
(address to, uint256 amount,, address asset,,) = ie.previewCommand(command);
assertEq(to, VITALIK_DOT_ETH);
assertEq(amount, 20 ether);
assertEq(asset, DAI);
Expand All @@ -90,20 +90,20 @@ contract IETest is Test {

function testPreviewCommandSendUSDC() public payable {
string memory command = "send z0r0z 20 usdc";
(address to, uint256 amount, address asset,,) = ie.previewCommand(command);
(address to, uint256 amount,, address asset,,) = ie.previewCommand(command);
assertEq(to, Z0R0Z_DOT_ETH);
assertEq(amount, 20000000);
assertEq(asset, USDC);
}

function testPreviewCommandSendDecimals() public payable {
string memory command = "send vitalik 20.2 dai";
(address to, uint256 amount, address asset,,) = ie.previewCommand(command);
(address to, uint256 amount,, address asset,,) = ie.previewCommand(command);
assertEq(to, VITALIK_DOT_ETH);
assertEq(amount, 20.2 ether);
assertEq(asset, DAI);
command = "send vitalik 20.23345 eth";
(to, amount, asset,,) = ie.previewCommand(command);
(to, amount,, asset,,) = ie.previewCommand(command);
assertEq(to, VITALIK_DOT_ETH);
assertEq(amount, 20.23345 ether);
assertEq(asset, ETH);
Expand Down Expand Up @@ -175,7 +175,7 @@ contract IETest is Test {

function testCommandSwapETH() public payable {
vm.prank(VITALIK_DOT_ETH);
ie.command{value: 10 ether}("swap 10 eth for dai");
ie.command{value: 10 ether}("swap 10 eth for 25000 dai"); //price might change in the future.
}

function testCommandSwapForETH() public payable {
Expand All @@ -200,7 +200,7 @@ contract IETest is Test {
vm.prank(USDC_WHALE);
IERC20(USDC).approve(address(ie), 100 ether);
vm.prank(USDC_WHALE);
ie.command("swap 100 usdc for weth");
ie.command("swap 100 usdc for 0.035 weth");
}

function testCommandSwapUSDCForWBTC() public payable {
Expand Down

0 comments on commit 0b0749c

Please sign in to comment.