如何用汇编实现基于chainId和合约地址生成URL以降低Gas?
低Gas开销的汇编实现方案
下面是完全通过内联汇编实现的URL生成函数,无需依赖OpenZeppelin的Strings.sol,部署Gas成本仅增加数千单位:
function url() public view returns (string memory) { uint256 chainId = block.chainid; address self = address(this); // 计算最终字符串长度:前缀(29) + chainId十进制位数 + 分隔符(1) + 地址十六进制(42) uint256 chainIdLen = _getDecimalLength(chainId); string memory result = new string(29 + chainIdLen + 1 + 42); assembly { // 指向字符串内容的起始位置(字符串结构体首字节是长度,偏移0x20才是内容) let ptr := add(result, 0x20) // 写入固定前缀 "https://website.com/token/" // 前缀的十六进制表示:0x68747470733a2f2f776562736974652e636f6d2f746f6b656e2f mstore(ptr, 0x68747470733a2f2f776562736974652e636f6d2f746f6b656e2f) ptr := add(ptr, 29) // 前缀实际长度29,移动指针到下一个写入位置 // 处理chainId的十进制转字符串 let num := chainId let tempPtr := add(ptr, 0x40) // 临时存储反转的数字字符 let len := chainIdLen // 将数字按位反转存入临时内存 for { let i := 0 } lt(i, len) { i := add(i, 1) } { mstore8(add(tempPtr, i), add(mod(num, 10), 0x30)) // 取余数转ASCII num := div(num, 10) } // 将反转的字符写回目标位置 for { let i := 0 } lt(i, len) { i := add(i, 1) } { mstore8(add(ptr, i), mload8(add(tempPtr, sub(len, i)))) } ptr := add(ptr, len) // 写入分隔符 "/" mstore8(ptr, 0x2F) ptr := add(ptr, 1) // 写入地址的十六进制字符串(带0x前缀) mstore8(ptr, 0x30) // '0' mstore8(add(ptr, 1), 0x78) // 'x' ptr := add(ptr, 2) let addr := self // 处理地址的每个字节,转两位十六进制字符 for { let i := 0 } lt(i, 20) { i := add(i, 1) } { let byteVal := and(shr(mul(sub(19, i), 8), addr), 0xFF) // 高4位转ASCII let highNibble := shr(4, byteVal) mstore8(add(ptr, mul(i, 2)), add(highNibble, lt(highNibble, 10) ? 0x30 : 0x57)) // 低4位转ASCII let lowNibble := and(byteVal, 0x0F) mstore8(add(ptr, add(mul(i, 2), 1)), add(lowNibble, lt(lowNibble, 10) ? 0x30 : 0x57)) } } return result; } // 计算uint256的十进制位数 function _getDecimalLength(uint256 num) internal pure returns (uint256) { if (num == 0) return 1; uint256 length = 0; while (num > 0) { length++; num /= 10; } return length; }
核心优化点:
- 直接操作内存写入固定前缀,避免字符串拼接的Gas消耗
- 手动实现十进制/十六进制转ASCII的逻辑,完全绕过第三方库的额外代码
- 所有内存操作通过汇编完成,最小化Gas开销
内容的提问来源于stack exchange,提问作者Cloudronaut
相关产品推荐
相关产品推荐

