Solidity Punycode without IDNA.
- Library: Punycode.sol
- ASCII strings:
~500 gas
(fastpath) - Unicode strings:
- Decode
~1300 gas/codepoint
- Encode
~2500 gas/codepoint
- Decode
- ASCII strings:
- Available on NPM:
import {Punycode} from "@adraffy/punycode-contracts/src/Punycode.sol";
- Foundry:
forge install adraffy/punycode.sol
- Suggested remapping:
@adraffy/punycode-contracts/=lib/Punycode.sol/
- Suggested remapping:
- Reference Implementation: adraffy/punycode.js
- Demo ⭐
- Deployment:
base:0xBEfeca057ea022e7aB419670a659d32f125973C1
- Deployment:
string memory unicode = Punycode.decode("xn--ls8h"); // "💩"
string memory punycode = Punycode.encode(unicode"💩"); // "xn--ls8h"
// reverts on failure
// let me know if a tryDecode() is needed
function decode(string memory s) pure returns (string memory);
// never fails
function encode(string memory s) pure returns (string memory);
Lower-level functions:
// src == dst if no encoding required
// otherwise, (dst-32) is effectively `bytes`
function decode(uint256 src, uint256 src_len) pure returns (uint256 dst, uint256 dst_len);
function encode(uint256 src, uint256 src_len) pure returns (uint256 dst, uint256 dst_len);
// example
string memory s = "abc.xn--ls8h.com";
uint256 src;
assembly { src := add(s, 32) }
(uint256 dst, uint256 len) = Punycode.decode(src + 4, 8); // "xn--ls8h"
console2.log(len); // 4 bytes
assembly { s := sub(dst, 32) }
console2.log(s); // "💩"
bytes32 h;
assembly { h := keccak256(dst, len) }
console2.logBytes32(h); // 0xba967c160905ade030f84952644a963994eeaed3881a6b8a4e9c8cbe452ad7a2
- (optional)
git submodule update --remote
foundryup
npm i
npm run test
— forge tests + random validation- (optional)
npm run validate-all
— complete validation - (optional)
npm run fuzz
— random inputs until cancelled
npm run gas
— estimate gasforge script GasEncode
— estimate gas forencode()
forge script GasDecode
— estimate gas fordecode()