Skip to content

Commit

Permalink
🤌 Refine str decimal conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
z0r0z committed May 29, 2024
1 parent 83941e8 commit 480bc67
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 79 deletions.
39 changes: 21 additions & 18 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
IETest:testCommandDepositETH() (gas: 155637)
IETest:testCommandSendETH() (gas: 77576)
IETest:testCommandSendETHRawAddr() (gas: 75267)
IETest:testCommandStakeETH() (gas: 147090)
IETest:testCommandSwapDAI() (gas: 168609)
IETest:testCommandSwapETH() (gas: 233248)
IETest:testCommandSwapForETH() (gas: 175502)
IETest:testCommandSwapUSDC() (gas: 171665)
IETest:testCommandSendETHRawAddr() (gas: 75289)
IETest:testCommandStakeETH() (gas: 147120)
IETest:testCommandSwapDAI() (gas: 138671)
IETest:testCommandSwapETH() (gas: 185596)
IETest:testCommandSwapForETH() (gas: 145586)
IETest:testCommandSwapUSDC() (gas: 171751)
IETest:testCommandSwapUSDCForWBTC() (gas: 196670)
IETest:testCommandUnstakeETH() (gas: 287967)
IETest:testCommandWithdrawETH() (gas: 290713)
IETest:testDeploy() (gas: 3703025)
IETest:testENSNameOwnership() (gas: 50674)
IETest:testIENameSetting() (gas: 11118)
IETest:testPreviewCommandSendDecimals() (gas: 111416)
IETest:testPreviewCommandSendUSDC() (gas: 70092)
IETest:testPreviewSend() (gas: 55972)
IETest:testPreviewSendCommand() (gas: 69618)
IETest:testPreviewSendCommandRawAddr() (gas: 66800)
IETest:testPreviewSendRawAddr() (gas: 29973)
IETest:testDeploy() (gas: 4000936)
IETest:testENSNameOwnership() (gas: 50696)
IETest:testPreviewCommandSendDecimals() (gas: 111460)
IETest:testPreviewCommandSendUSDC() (gas: 70114)
IETest:testPreviewSend() (gas: 56016)
IETest:testPreviewSendCommand() (gas: 69640)
IETest:testPreviewSendCommandRawAddr() (gas: 66822)
IETest:testPreviewSendRawAddr() (gas: 30017)
IETest:testTokenAliasSetting() (gas: 10964)
IETest:testTranslateCommand() (gas: 10531)
IETest:testTranslateExecuteSend0_0_1ETH() (gas: 29102)
IETest:testTranslateExecuteSend0_1ETH() (gas: 28475)
IETest:testTranslateExecuteSend10USDC() (gas: 26870)
IETest:testTranslateExecuteSend1ETH() (gas: 30784)
IETest:testTranslateExecuteSend1Wei() (gas: 32438)
NAMITest:testFailRegister() (gas: 9532)
NAMITest:testRegister() (gas: 59011)
2 changes: 1 addition & 1 deletion lib/solady
Submodule solady updated 2 files
+753 −753 .gas-snapshot
+31 −21 src/accounts/ERC1271.sol
141 changes: 105 additions & 36 deletions src/IE.sol
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,7 @@ contract IE {
_extractSend(normalized);
(to, amount, token, callData, executeCallData) = previewSend(_to, _amount, _token);
} else if (
action == "swap" || action == "exchange" || action == "stake" || action == "deposit"
|| action == "unstake" || action == "withdraw"
action == "swap" || action == "sell" || action == "exchange" || action == "stake"
) {
(
string memory amountIn,
Expand Down Expand Up @@ -254,7 +253,7 @@ contract IE {
public
view
virtual
returns (bool)
returns (bool intentMatched)
{
(,,,,, bytes memory executeCallData) = previewCommand(intent);
if (executeCallData.length != userOp.callData.length) return false;
Expand All @@ -266,7 +265,7 @@ contract IE {
public
view
virtual
returns (bool)
returns (bool intentMatched)
{
(,,,,, bytes memory executeCallData) = previewCommand(intent);
if (executeCallData.length != userOp.callData.length) return false;
Expand Down Expand Up @@ -336,8 +335,7 @@ contract IE {
(string memory to, string memory amount, string memory token) = _extractSend(normalized);
send(to, amount, token);
} else if (
action == "swap" || action == "exchange" || action == "stake" || action == "deposit"
|| action == "unstake" || action == "withdraw"
action == "swap" || action == "sell" || action == "exchange" || action == "stake"
) {
(
string memory amountIn,
Expand Down Expand Up @@ -546,9 +544,14 @@ contract IE {

/// ==================== COMMAND TRANSLATION ==================== ///

/// @dev Translates the `intent` for send action from the solution `callData` of a standard `execute()`.
/// @dev Translates an `intent` from raw `command()` calldata.
function translateCommand(bytes calldata callData) public pure returns (string memory intent) {
return string(callData[4:]);
}

/// @dev Translates an `intent` for send action from the solution `callData` of standard `execute()`.
/// note: The function selector technically doesn't need to be `execute()` but params should match.
function translate(bytes calldata callData)
function translateExecute(bytes calldata callData)
public
view
virtual
Expand All @@ -560,13 +563,19 @@ contract IE {
if (value != 0) {
return string(
abi.encodePacked(
"send ", _toString(value / 10 ** 18), " ETH to 0x", _toAsciiString(target)
"send ",
_convertWeiToString(value, 18),
" ETH to 0x",
_toAsciiString(target)
)
);
}

// The userOp `execute()` calldata must be a call to the ERC20 `transfer()` method.
if (bytes4(callData[132:136]) != IToken.transfer.selector) revert InvalidSelector();
if (
bytes4(callData[132:136]) != IToken.transfer.selector
&& bytes4(callData[132:136]) != IToken.approve.selector
) revert InvalidSelector();
bool transfer = bytes4(callData[132:136]) == IToken.transfer.selector;

(string memory token, uint256 decimals) = _returnTokenAliasConstants(target);
if (bytes(token).length == 0) token = aliases[target];
Expand All @@ -575,8 +584,8 @@ contract IE {

return string(
abi.encodePacked(
"send ",
_toString(value / 10 ** decimals),
transfer ? "send " : "approve ",
_convertWeiToString(value, decimals),
" ",
token,
" to 0x",
Expand All @@ -594,44 +603,52 @@ contract IE {
virtual
returns (string memory intent)
{
// The token calldata must be a call to the ERC20 `transfer()` method.
if (bytes4(tokenCalldata) != IToken.transfer.selector) revert InvalidSelector();

(string memory tokenAlias, uint256 decimals) = _returnTokenAliasConstants(token);
if (bytes(tokenAlias).length == 0) tokenAlias = aliases[token];
if (decimals == 0) decimals = token.readDecimals(); // Sanity check.
(address target, uint256 value) = abi.decode(tokenCalldata[4:], (address, uint256));

return string(
abi.encodePacked(
"send ",
_toString(value / 10 ** decimals),
" ",
token,
" to 0x",
_toAsciiString(target)
)
);
unchecked {
if (
bytes4(tokenCalldata) != IToken.transfer.selector
&& bytes4(tokenCalldata) != IToken.approve.selector
) revert InvalidSelector();
bool transfer = bytes4(tokenCalldata) == IToken.transfer.selector;
(string memory tokenAlias, uint256 decimals) = _returnTokenAliasConstants(token);
if (bytes(tokenAlias).length == 0) tokenAlias = aliases[token];
if (decimals == 0) decimals = token.readDecimals(); // Sanity check.
(address target, uint256 value) = abi.decode(tokenCalldata[4:], (address, uint256));

return string(
abi.encodePacked(
transfer ? "send " : "approve ",
_convertWeiToString(value, decimals),
" ",
token,
" to 0x",
_toAsciiString(target)
)
);
}
}

/// @dev Translate ERC4337 userOp `callData` into readable send `intent`.
/// @dev Translate ERC4337 userOp `callData` into readable `intent`.
function translateUserOp(UserOperation calldata userOp)
public
view
virtual
returns (string memory intent)
{
return translate(userOp.callData);
return bytes4(userOp.callData) == IExecutor.execute.selector
? translateExecute(userOp.callData)
: translateCommand(userOp.callData);
}

/// @dev Translate packed ERC4337 userOp `callData` into readable send `intent`.
/// @dev Translate packed ERC4337 userOp `callData` into readable `intent`.
function translatePackedUserOp(PackedUserOperation calldata userOp)
public
view
virtual
returns (string memory intent)
{
return translate(userOp.callData);
return bytes4(userOp.callData) == IExecutor.execute.selector
? translateExecute(userOp.callData)
: translateCommand(userOp.callData);
}

/// ================== BALANCE & SUPPLY HELPERS ================== ///
Expand Down Expand Up @@ -927,6 +944,57 @@ contract IE {
}
}

/// @dev Convert number to string and insert decimal point.
function _convertWeiToString(uint256 weiAmount, uint256 decimals)
internal
pure
virtual
returns (string memory)
{
unchecked {
uint256 scalingFactor = 10 ** decimals;

string memory wholeNumberStr = _toString(weiAmount / scalingFactor);
string memory decimalPartStr = _toString(weiAmount % scalingFactor);

while (bytes(decimalPartStr).length != decimals) {
decimalPartStr = string(abi.encodePacked("0", decimalPartStr));
}

decimalPartStr = _removeTrailingZeros(decimalPartStr);

if (bytes(decimalPartStr).length == 0) {
return wholeNumberStr;
}

return string(abi.encodePacked(wholeNumberStr, ".", decimalPartStr));
}
}

/// @dev Remove any trailing zeroes from string.
function _removeTrailingZeros(string memory str)
internal
pure
virtual
returns (string memory)
{
unchecked {
bytes memory strBytes = bytes(str);
uint256 end = strBytes.length;

while (end != 0 && strBytes[end - 1] == "0") {
--end;
}

bytes memory trimmedBytes = new bytes(end);
for (uint256 i; i != end; ++i) {
trimmedBytes[i] = strBytes[i];
}

return string(trimmedBytes);
}
}

/// @dev Returns the base 10 decimal representation of `value`.
/// Modified from (https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol)
function _toString(uint256 value) internal pure virtual returns (string memory str) {
Expand All @@ -949,8 +1017,9 @@ contract IE {
}
}

/// @dev Simple token transfer interface.
/// @dev Simple token handler interface.
interface IToken {
function approve(address, uint256) external returns (bool);
function transfer(address, uint256) external returns (bool);
}

Expand Down
85 changes: 61 additions & 24 deletions test/IE.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ contract IETest is Test {
assertEq(asset, ETH);
}

function testIENameSetting() public payable {
function testTokenAliasSetting() public payable {
assertEq(ie.tokens("usdc"), USDC);
}

Expand All @@ -138,29 +138,6 @@ contract IETest is Test {
ie.command{value: 1 ether}("stake 1 eth into lido");
}

function testCommandDepositETH() public payable {
vm.prank(VITALIK_DOT_ETH);
ie.command{value: 1 ether}("deposit 1 eth into reth");
}

function testCommandWithdrawETH() public payable {
vm.prank(VITALIK_DOT_ETH);
ie.command{value: 1 ether}("deposit 1 eth into reth");
vm.prank(VITALIK_DOT_ETH);
IERC20(RETH).approve(address(ie), 100 ether);
vm.prank(VITALIK_DOT_ETH);
ie.command("withdraw 0.8 reth into eth");
}

function testCommandUnstakeETH() public payable {
vm.prank(VITALIK_DOT_ETH);
ie.command{value: 1 ether}("stake 1 eth into reth");
vm.prank(VITALIK_DOT_ETH);
IERC20(RETH).approve(address(ie), 100 ether);
vm.prank(VITALIK_DOT_ETH);
ie.command("unstake 0.8 reth for eth");
}

function testCommandSwapForETH() public payable {
uint256 startBalETH = DAI_WHALE.balance;
uint256 startBalDAI = IERC20(DAI).balanceOf(DAI_WHALE);
Expand Down Expand Up @@ -196,9 +173,69 @@ contract IETest is Test {
assert(startBalWBTC < IERC20(WBTC).balanceOf(USDC_WHALE));
assertEq(startBalUSDC - 100 * 10 ** 6, IERC20(USDC).balanceOf(USDC_WHALE));
}

function testTranslateCommand() public payable {
string memory intent = "send z0r0z 1 usdc";
string memory ret = ie.translateCommand(abi.encodePacked(ie.command.selector, intent));
assertEq(ret, intent);
}

function testTranslateExecuteSend1ETH() public payable {
string memory intent = "send 1 ETH to 0x1c0aa8ccd568d90d61659f060d1bfb1e6f855a20";
bytes4 sig = IExecutor.execute.selector;
bytes memory execData = abi.encode(Z0R0Z_DOT_ETH, 1 ether, "");
execData = abi.encodePacked(sig, execData);
string memory ret = ie.translateExecute(execData);
assertEq(ret, intent);
}

function testTranslateExecuteSend0_1ETH() public payable {
string memory intent = "send 0.1 ETH to 0x1c0aa8ccd568d90d61659f060d1bfb1e6f855a20";
bytes4 sig = IExecutor.execute.selector;
bytes memory execData = abi.encode(Z0R0Z_DOT_ETH, 100000000000000000, "");
execData = abi.encodePacked(sig, execData);
string memory ret = ie.translateExecute(execData);
assertEq(ret, intent);
}

function testTranslateExecuteSend0_0_1ETH() public payable {
string memory intent = "send 0.01 ETH to 0x1c0aa8ccd568d90d61659f060d1bfb1e6f855a20";
bytes4 sig = IExecutor.execute.selector;
bytes memory execData = abi.encode(Z0R0Z_DOT_ETH, 10000000000000000, "");
execData = abi.encodePacked(sig, execData);
string memory ret = ie.translateExecute(execData);
assertEq(ret, intent);
}

function testTranslateExecuteSend1Wei() public payable {
string memory intent =
"send 0.000000000000000001 ETH to 0x1c0aa8ccd568d90d61659f060d1bfb1e6f855a20";
bytes4 sig = IExecutor.execute.selector;
bytes memory execData = abi.encode(Z0R0Z_DOT_ETH, 1, "");
execData = abi.encodePacked(sig, execData);
string memory ret = ie.translateExecute(execData);
assertEq(ret, intent);
}

function testTranslateExecuteSend10USDC() public payable {
string memory intent = "send 10 USDC to 0x1c0aa8ccd568d90d61659f060d1bfb1e6f855a20";
bytes memory execData = abi.encodeWithSelector(
IExecutor.execute.selector,
USDC,
0,
abi.encodeWithSelector(IERC20.transfer.selector, Z0R0Z_DOT_ETH, 10000000)
);
string memory ret = ie.translateExecute(execData);
assertEq(ret, intent);
}
}

interface IERC20 {
function approve(address, uint256) external; // unsafe lol.
function balanceOf(address) external view returns (uint256);
function transfer(address, uint256) external returns (bool);
}

interface IExecutor {
function execute(address, uint256, bytes calldata) external payable returns (bytes memory);
}

0 comments on commit 480bc67

Please sign in to comment.