diff --git a/.gas-snapshot b/.gas-snapshot index c0fa0d3..352b093 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,22 +1,22 @@ -IETest:testCommandDepositETH() (gas: 155598) -IETest:testCommandSendETH() (gas: 75320) -IETest:testCommandSendETHRawAddr() (gas: 75755) -IETest:testCommandStakeETH() (gas: 147756) -IETest:testCommandSwapDAI() (gas: 138503) -IETest:testCommandSwapETH() (gas: 138209) -IETest:testCommandSwapForETH() (gas: 145404) -IETest:testCommandSwapUSDC() (gas: 171497) -IETest:testCommandSwapUSDCForWBTC() (gas: 196200) -IETest:testCommandUnstakeETH() (gas: 263506) -IETest:testCommandWithdrawETH() (gas: 266209) -IETest:testDeploy() (gas: 2563979) -IETest:testENSNameOwnership() (gas: 48424) -IETest:testIENameSetting() (gas: 11105) -IETest:testPreviewCommandSendDecimals() (gas: 108639) -IETest:testPreviewCommandSendUSDC() (gas: 67684) -IETest:testPreviewSend() (gas: 53800) -IETest:testPreviewSendCommand() (gas: 67248) -IETest:testPreviewSendCommandRawAddr() (gas: 67151) -IETest:testPreviewSendRawAddr() (gas: 30543) -NAMITest:testFailRegister() (gas: 9471) -NAMITest:testRegister() (gas: 58853) \ No newline at end of file +IETest:testCommandDepositETH() (gas: 164220) +IETest:testCommandSendETH() (gas: 77505) +IETest:testCommandSendETHRawAddr() (gas: 75196) +IETest:testCommandStakeETH() (gas: 146991) +IETest:testCommandSwapDAI() (gas: 138570) +IETest:testCommandSwapETH() (gas: 233185) +IETest:testCommandSwapForETH() (gas: 145463) +IETest:testCommandSwapUSDC() (gas: 171664) +IETest:testCommandSwapUSDCForWBTC() (gas: 196531) +IETest:testCommandUnstakeETH() (gas: 297247) +IETest:testCommandWithdrawETH() (gas: 299993) +IETest:testDeploy() (gas: 3669933) +IETest:testENSNameOwnership() (gas: 50652) +IETest:testIENameSetting() (gas: 11118) +IETest:testPreviewCommandSendDecimals() (gas: 111274) +IETest:testPreviewCommandSendUSDC() (gas: 70021) +IETest:testPreviewSend() (gas: 55972) +IETest:testPreviewSendCommand() (gas: 69547) +IETest:testPreviewSendCommandRawAddr() (gas: 66729) +IETest:testPreviewSendRawAddr() (gas: 29973) +NAMITest:testFailRegister() (gas: 9532) +NAMITest:testRegister() (gas: 59011) \ No newline at end of file diff --git a/docs/src/src/IE.sol/contract.IE.md b/docs/src/src/IE.sol/contract.IE.md index ccb90ab..8fa9c1b 100644 --- a/docs/src/src/IE.sol/contract.IE.md +++ b/docs/src/src/IE.sol/contract.IE.md @@ -1,5 +1,5 @@ # IE -[Git Source](https://github.com/NaniDAO/ie/blob/f061f69f55a660146bbc3247dded252faef04a99/src/IE.sol) +[Git Source](https://github.com/NaniDAO/ie/blob/d47449524e79a44fee3444e5d49a8256f0cc4165/src/IE.sol) **Author:** nani.eth (https://github.com/NaniDAO/ie) @@ -150,19 +150,19 @@ uint160 internal constant MAX_SQRT_RATIO_MINUS_ONE = ``` -### NAMI -*The NAMI naming system on Arbitrum.* +### nami +========================== STORAGE ========================== /// + +*DAO-governed NAMI naming system on Arbitrum.* ```solidity -INAMI internal constant NAMI = INAMI(0x000000006641B4C250AEA6B62A1e0067D300697a); +INAMI internal nami = INAMI(0x000000006641B4C250AEA6B62A1e0067D300697a); ``` ### tokens -========================== STORAGE ========================== /// - -*DAO-governed token address naming.* +*DAO-governed token name aliasing.* ```solidity @@ -170,6 +170,15 @@ mapping(string name => address) public tokens; ``` +### aliases +*DAO-governed token address name aliasing.* + + +```solidity +mapping(address token => string name) public aliases; +``` + + ### pairs *DAO-governed token swap pool routing on Uniswap V3.* @@ -291,6 +300,19 @@ function _returnTokenConstants(bytes32 token) returns (address _token, uint256 _decimals); ``` +### _returnTokenAliasConstants + +*Checks and returns the canonical token string constant for a matched address.* + + +```solidity +function _returnTokenAliasConstants(address token) + internal + pure + virtual + returns (string memory _token, uint256 _decimals); +``` + ### _returnPoolConstants *Checks and returns popular pool pairs for WETH swaps.* @@ -416,6 +438,44 @@ Only canonical WETH can call.* receive() external payable virtual; ``` +### translate + +==================== COMMAND TRANSLATION ==================== /// + +*Translates the `intent` for send action from the solution `callData` of a standard `execute()`. +note: The function selector technically doesn't need to be `execute()` but params should match.* + + +```solidity +function translate(bytes calldata callData) public view virtual returns (string memory intent); +``` + +### translateUserOp + +*Translate ERC4337 userOp `callData` into readable send `intent`.* + + +```solidity +function translateUserOp(UserOperation calldata userOp) + public + view + virtual + returns (string memory intent); +``` + +### translatePackedUserOp + +*Translate packed ERC4337 userOp `callData` into readable send `intent`.* + + +```solidity +function translatePackedUserOp(PackedUserOperation calldata userOp) + public + view + virtual + returns (string memory intent); +``` + ### whatIsTheBalanceOf ================== BALANCE & SUPPLY HELPERS ================== /// @@ -459,24 +519,24 @@ function whatIsTheAddressOf(string memory name) returns (address owner, address receiver, bytes32 node); ``` -### setName +### setAlias ========================= GOVERNANCE ========================= /// -*Sets a public `name` tag for a given `token` address. Governed by DAO.* +*Sets a public alias tag for a given `token` address. Governed by DAO.* ```solidity -function setName(address token, string calldata name) public payable virtual; +function setAlias(address token, string calldata _alias) public payable virtual; ``` -### setNameAndTicker +### setAliasAndTicker -*Sets a public name and ticker for a given `token` address.* +*Sets a public alias and ticker for a given `token` address.* ```solidity -function setNameAndTicker(address token) public payable virtual; +function setAliasAndTicker(address token) public payable virtual; ``` ### setPair @@ -488,6 +548,15 @@ function setNameAndTicker(address token) public payable virtual; function setPair(address tokenA, address tokenB, address pair) public payable virtual; ``` +### setNAMI + +*Sets the Arbitrum naming singleton (NAMI). Governed by DAO.* + + +```solidity +function setNAMI(INAMI NAMI) public payable virtual; +``` + ### _lowercase ===================== STRING OPERATIONS ===================== /// @@ -598,15 +667,43 @@ function _hexStringToAddress(string memory s) internal pure virtual returns (byt function _fromHexChar(uint8 c) internal pure virtual returns (uint8 result); ``` +### _toAsciiString + +*Convert an address to an ASCII string representation.* + + +```solidity +function _toAsciiString(address x) internal pure virtual returns (string memory); +``` + +### _char + +*Convert a single byte to a character in the ASCII string.* + + +```solidity +function _char(bytes1 b) internal pure virtual returns (bytes1 c); +``` + +### _toString + +*Returns the base 10 decimal representation of `value`. +Modified from (https://github.com/Vectorized/solady/blob/main/src/utils/LibString.sol)* + + +```solidity +function _toString(uint256 value) internal pure virtual returns (string memory str); +``` + ## Events -### NameSet +### AliasSet =========================== EVENTS =========================== /// -*Logs the registration of a token name.* +*Logs the registration of a token name alias.* ```solidity -event NameSet(address indexed token, string name); +event AliasSet(address indexed token, string name); ``` ### PairSet @@ -621,9 +718,9 @@ event PairSet(address indexed token0, address indexed token1, address pair); ### Overflow ======================= LIBRARY USAGE ======================= /// -*Metadata reader library.* +*Token transfer library.* -*Safe token transfer library. +*Token metadata reader library. ======================= CUSTOM ERRORS ======================= ///* *Bad math.* @@ -665,6 +762,14 @@ error InvalidCharacter(); error InsufficientSwap(); ``` +### InvalidSelector +*Invalid selector for the given asset spend.* + + +```solidity +error InvalidSelector(); +``` + ## Structs ### UserOperation ========================== STRUCTS ========================== /// diff --git a/docs/src/src/IE.sol/interface.IExecutor.md b/docs/src/src/IE.sol/interface.IExecutor.md index 5aaf28f..6a0c600 100644 --- a/docs/src/src/IE.sol/interface.IExecutor.md +++ b/docs/src/src/IE.sol/interface.IExecutor.md @@ -1,5 +1,5 @@ # IExecutor -[Git Source](https://github.com/NaniDAO/ie/blob/f061f69f55a660146bbc3247dded252faef04a99/src/IE.sol) +[Git Source](https://github.com/NaniDAO/ie/blob/d47449524e79a44fee3444e5d49a8256f0cc4165/src/IE.sol) Simple calldata executor interface. diff --git a/docs/src/src/IE.sol/interface.INAMI.md b/docs/src/src/IE.sol/interface.INAMI.md index 3b13333..c6541ce 100644 --- a/docs/src/src/IE.sol/interface.INAMI.md +++ b/docs/src/src/IE.sol/interface.INAMI.md @@ -1,5 +1,5 @@ # INAMI -[Git Source](https://github.com/NaniDAO/ie/blob/f061f69f55a660146bbc3247dded252faef04a99/src/IE.sol) +[Git Source](https://github.com/NaniDAO/ie/blob/d47449524e79a44fee3444e5d49a8256f0cc4165/src/IE.sol) *Simple NAMI names interface for resolving L2 ENS ownership.* diff --git a/docs/src/src/IE.sol/interface.ISwapRouter.md b/docs/src/src/IE.sol/interface.ISwapRouter.md index b4801cd..f9f4492 100644 --- a/docs/src/src/IE.sol/interface.ISwapRouter.md +++ b/docs/src/src/IE.sol/interface.ISwapRouter.md @@ -1,5 +1,5 @@ # ISwapRouter -[Git Source](https://github.com/NaniDAO/ie/blob/f061f69f55a660146bbc3247dded252faef04a99/src/IE.sol) +[Git Source](https://github.com/NaniDAO/ie/blob/d47449524e79a44fee3444e5d49a8256f0cc4165/src/IE.sol) *Simple Uniswap V3 swapping interface.* diff --git a/docs/src/src/IE.sol/interface.IToken.md b/docs/src/src/IE.sol/interface.IToken.md index c57ed0e..4f4eb6c 100644 --- a/docs/src/src/IE.sol/interface.IToken.md +++ b/docs/src/src/IE.sol/interface.IToken.md @@ -1,5 +1,5 @@ # IToken -[Git Source](https://github.com/NaniDAO/ie/blob/f061f69f55a660146bbc3247dded252faef04a99/src/IE.sol) +[Git Source](https://github.com/NaniDAO/ie/blob/d47449524e79a44fee3444e5d49a8256f0cc4165/src/IE.sol) *Simple token transfer interface.* diff --git a/docs/src/src/NAMI.sol/contract.NAMI.md b/docs/src/src/NAMI.sol/contract.NAMI.md index bfdfd92..4f5f5a5 100644 --- a/docs/src/src/NAMI.sol/contract.NAMI.md +++ b/docs/src/src/NAMI.sol/contract.NAMI.md @@ -1,5 +1,5 @@ # NAMI -[Git Source](https://github.com/NaniDAO/ie/blob/f061f69f55a660146bbc3247dded252faef04a99/src/NAMI.sol) +[Git Source](https://github.com/NaniDAO/ie/blob/d47449524e79a44fee3444e5d49a8256f0cc4165/src/NAMI.sol) **Author:** nani.eth (https://github.com/NaniDAO/ie) diff --git a/docs/src/src/NAMI.sol/interface.IToken.md b/docs/src/src/NAMI.sol/interface.IToken.md index 526fecb..23f74e2 100644 --- a/docs/src/src/NAMI.sol/interface.IToken.md +++ b/docs/src/src/NAMI.sol/interface.IToken.md @@ -1,5 +1,5 @@ # IToken -[Git Source](https://github.com/NaniDAO/ie/blob/f061f69f55a660146bbc3247dded252faef04a99/src/NAMI.sol) +[Git Source](https://github.com/NaniDAO/ie/blob/d47449524e79a44fee3444e5d49a8256f0cc4165/src/NAMI.sol) *Simple token balance & supply interface.* diff --git a/foundry.toml b/foundry.toml index 22929b7..c22930c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] -solc_version = "0.8.25" +solc_version = "0.8.26" evm_version = "cancun" optimizer = true diff --git a/lib/forge-std b/lib/forge-std index e4aef94..52715a2 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit e4aef94c1768803a16fe19f7ce8b65defd027cfd +Subproject commit 52715a217dc51d0de15877878ab8213f6cbbbab5 diff --git a/lib/solady b/lib/solady index e4a14a5..0dde2a0 160000 --- a/lib/solady +++ b/lib/solady @@ -1 +1 @@ -Subproject commit e4a14a5b365b353352f7c38e699a2bc9363d6576 +Subproject commit 0dde2a008d917aa8076f348eac2855edbe181cc0 diff --git a/src/IE.sol b/src/IE.sol index 7b857de..7fb6a91 100644 --- a/src/IE.sol +++ b/src/IE.sol @@ -10,16 +10,16 @@ import {MetadataReaderLib} from "../lib/solady/src/utils/MetadataReaderLib.sol"; /// @dev V1 simulates typical commands (sending and swapping tokens) and includes execution. /// IE also has a workflow to verify the intent of ERC4337 account userOps against calldata. /// @author nani.eth (https://github.com/NaniDAO/ie) -/// @custom:version 1.2.0 +/// @custom:version 1.3.0 contract IE { /// ======================= LIBRARY USAGE ======================= /// - /// @dev Metadata reader library. - using MetadataReaderLib for address; - - /// @dev Safe token transfer library. + /// @dev Token transfer library. using SafeTransferLib for address; + /// @dev Token metadata reader library. + using MetadataReaderLib for address; + /// ======================= CUSTOM ERRORS ======================= /// /// @dev Bad math. @@ -37,10 +37,13 @@ contract IE { /// @dev Insufficient swap output. error InsufficientSwap(); + /// @dev Invalid selector for the given asset spend. + error InvalidSelector(); + /// =========================== EVENTS =========================== /// - /// @dev Logs the registration of a token name. - event NameSet(address indexed token, string name); + /// @dev Logs the registration of a token name alias. + event AliasSet(address indexed token, string name); /// @dev Logs the registration of a token swap pool pair route on Uniswap V3. event PairSet(address indexed token0, address indexed token1, address pair); @@ -139,14 +142,17 @@ contract IE { uint160 internal constant MAX_SQRT_RATIO_MINUS_ONE = 1461446703485210103287273052203988822378723970341; - /// @dev The NAMI naming system on Arbitrum. - INAMI internal constant NAMI = INAMI(0x000000006641B4C250AEA6B62A1e0067D300697a); - /// ========================== STORAGE ========================== /// - /// @dev DAO-governed token address naming. + /// @dev DAO-governed NAMI naming system on Arbitrum. + INAMI internal nami = INAMI(0x000000006641B4C250AEA6B62A1e0067D300697a); + + /// @dev DAO-governed token name aliasing. mapping(string name => address) public tokens; + /// @dev DAO-governed token address name aliasing. + mapping(address token => string name) public aliases; + /// @dev DAO-governed token swap pool routing on Uniswap V3. mapping(address token0 => mapping(address token1 => address)) public pairs; @@ -286,6 +292,24 @@ contract IE { if (token == "nani") return (NANI, 18); } + /// @dev Checks and returns the canonical token string constant for a matched address. + function _returnTokenAliasConstants(address token) + internal + pure + virtual + returns (string memory _token, uint256 _decimals) + { + if (token == USDC) return ("USDC", 6); + if (token == USDT) return ("USDT", 6); + if (token == DAI) return ("DAI", 18); + if (token == ARB) return ("ARB", 18); + if (token == WETH) return ("WETH", 18); + if (token == WBTC) return ("WBTC", 8); + if (token == WSTETH) return ("WSTETH", 18); + if (token == RETH) return ("RETH", 18); + if (token == NANI) return ("NANI", 18); + } + /// @dev Checks and returns popular pool pairs for WETH swaps. function _returnPoolConstants(address token0, address token1) internal @@ -520,6 +544,68 @@ contract IE { } } + /// ==================== COMMAND TRANSLATION ==================== /// + + /// @dev Translates the `intent` for send action from the solution `callData` of a standard `execute()`. + /// note: The function selector technically doesn't need to be `execute()` but params should match. + function translate(bytes calldata callData) + public + view + virtual + returns (string memory intent) + { + unchecked { + (address target, uint256 value) = abi.decode(callData[4:68], (address, uint256)); + + if (value != 0) { + return string( + abi.encodePacked( + "send ", _toString(value / 10 ** 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(); + + (string memory token, uint256 decimals) = _returnTokenAliasConstants(target); + if (bytes(token).length == 0) token = aliases[target]; + if (decimals == 0) decimals = target.readDecimals(); // Sanity check. + (target, value) = abi.decode(callData[136:], (address, uint256)); + + return string( + abi.encodePacked( + "send ", + _toString(value / 10 ** decimals), + " ", + token, + " to 0x", + _toAsciiString(target) + ) + ); + } + } + + /// @dev Translate ERC4337 userOp `callData` into readable send `intent`. + function translateUserOp(UserOperation calldata userOp) + public + view + virtual + returns (string memory intent) + { + return translate(userOp.callData); + } + + /// @dev Translate packed ERC4337 userOp `callData` into readable send `intent`. + function translatePackedUserOp(PackedUserOperation calldata userOp) + public + view + virtual + returns (string memory intent) + { + return translate(userOp.callData); + } + /// ================== BALANCE & SUPPLY HELPERS ================== /// /// @dev Returns the balance of a named account in a named token. @@ -570,27 +656,29 @@ contract IE { if (bytes(name).length == 42) { receiver = _toAddress(name); } else { - (owner, receiver, node) = NAMI.whatIsTheAddressOf(name); + (owner, receiver, node) = nami.whatIsTheAddressOf(name); } } /// ========================= GOVERNANCE ========================= /// - /// @dev Sets a public `name` tag for a given `token` address. Governed by DAO. - function setName(address token, string calldata name) public payable virtual { + /// @dev Sets a public alias tag for a given `token` address. Governed by DAO. + function setAlias(address token, string calldata _alias) public payable virtual { assembly ("memory-safe") { if iszero(eq(caller(), DAO)) { revert(codesize(), 0x00) } // Optimized for repeat. } - string memory normalized = _lowercase(name); - emit NameSet(tokens[normalized] = token, normalized); + string memory normalized = _lowercase(_alias); + aliases[token] = _alias; + emit AliasSet(tokens[normalized] = token, normalized); } - /// @dev Sets a public name and ticker for a given `token` address. - function setNameAndTicker(address token) public payable virtual { + /// @dev Sets a public alias and ticker for a given `token` address. + function setAliasAndTicker(address token) public payable virtual { string memory normalizedName = _lowercase(token.readName()); string memory normalizedSymbol = _lowercase(token.readSymbol()); - emit NameSet(tokens[normalizedName] = token, normalizedName); - emit NameSet(tokens[normalizedSymbol] = token, normalizedSymbol); + aliases[token] = normalizedSymbol; + emit AliasSet(tokens[normalizedName] = token, normalizedName); + emit AliasSet(tokens[normalizedSymbol] = token, normalizedSymbol); } /// @dev Sets a public pool `pair` for swapping. Governed by DAO. @@ -602,6 +690,14 @@ contract IE { emit PairSet(tokenA, tokenB, pairs[tokenA][tokenB] = pair); } + /// @dev Sets the Arbitrum naming singleton (NAMI). Governed by DAO. + function setNAMI(INAMI NAMI) public payable virtual { + assembly ("memory-safe") { + if iszero(eq(caller(), DAO)) { revert(codesize(), 0x00) } // Optimized for repeat. + } + nami = NAMI; // No event emitted since very infrequent if ever. + } + /// ===================== STRING OPERATIONS ===================== /// /// @dev Returns copy of string in lowercase. @@ -779,6 +875,50 @@ contract IE { } } } + + /// @dev Convert an address to an ASCII string representation. + function _toAsciiString(address x) internal pure virtual returns (string memory) { + unchecked { + bytes memory s = new bytes(40); + for (uint256 i; i < 20; ++i) { + bytes1 b = bytes1(uint8(uint256(uint160(x)) / (2 ** (8 * (19 - i))))); + bytes1 hi = bytes1(uint8(b) / 16); + bytes1 lo = bytes1(uint8(b) - 16 * uint8(hi)); + s[2 * i] = _char(hi); + s[2 * i + 1] = _char(lo); + } + return string(s); + } + } + + /// @dev Convert a single byte to a character in the ASCII string. + function _char(bytes1 b) internal pure virtual returns (bytes1 c) { + unchecked { + if (uint8(b) < 10) return bytes1(uint8(b) + 0x30); + else return bytes1(uint8(b) + 0x57); + } + } + + /// @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) { + assembly ("memory-safe") { + str := add(mload(0x40), 0x80) + mstore(0x40, add(str, 0x20)) + mstore(str, 0) + let end := str + let w := not(0) + for { let temp := value } 1 {} { + str := add(str, w) + mstore8(str, add(48, mod(temp, 10))) + temp := div(temp, 10) + if iszero(temp) { break } + } + let length := sub(end, str) + str := sub(str, 0x20) + mstore(str, length) + } + } } /// @dev Simple token transfer interface. diff --git a/test/IE.t.sol b/test/IE.t.sol index 2b6a1e8..c9f9070 100644 --- a/test/IE.t.sol +++ b/test/IE.t.sol @@ -18,7 +18,7 @@ contract IETest is Test { address internal constant WSTETH = 0x5979D7b546E38E414F7E9822514be443A4800529; address internal constant RETH = 0xEC70Dcb4A1EFa46b8F2D97C310C9c4790ba5ffA8; - address internal constant VITALIK_DOT_ETH = 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045; + address internal constant VITALIK_DOT_ETH = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789; address internal constant Z0R0Z_DOT_ETH = 0x1C0Aa8cCD568d90d61659F060D1bFb1e6f855A20; address internal constant NANI_DOT_ETH = 0x7AF890Ca7262D6accdA5c9D24AC42e35Bb293188; @@ -34,25 +34,25 @@ contract IETest is Test { vm.createSelectFork(vm.rpcUrl("arbi")); // Arbitrum fork. ie = new IE(); vm.prank(DAO); - ie.setName(ETH, "ETH"); + ie.setAlias(ETH, "ETH"); vm.prank(DAO); - ie.setName(ETH, "ether"); + ie.setAlias(ETH, "ether"); vm.prank(DAO); - ie.setName(ETH, "ethereum"); + ie.setAlias(ETH, "ethereum"); vm.prank(DAO); - ie.setName(DAI, "DAI"); + ie.setAlias(DAI, "DAI"); vm.prank(DAO); - ie.setName(USDC, "USDC"); + ie.setAlias(USDC, "USDC"); vm.prank(DAO); - ie.setName(WETH, "WETH"); + ie.setAlias(WETH, "WETH"); vm.prank(DAO); - ie.setName(WETH, "wrapped eth"); + ie.setAlias(WETH, "wrapped eth"); vm.prank(DAO); - ie.setName(WETH, "wrapped ether"); + ie.setAlias(WETH, "wrapped ether"); vm.prank(DAO); - ie.setName(USDT, "USDT"); + ie.setAlias(USDT, "USDT"); vm.prank(DAO); - ie.setName(USDT, "tether"); + ie.setAlias(USDT, "tether"); } function testDeploy() public payable {