github.com/TrueBlocks/trueblocks-core/src/apps/chifra@v0.0.0-20241022031540-b362680128f7/pkg/decode/string.go (about) 1 package decode 2 3 import ( 4 "fmt" 5 6 "github.com/TrueBlocks/trueblocks-core/src/apps/chifra/pkg/base" 7 goAbi "github.com/ethereum/go-ethereum/accounts/abi" 8 ) 9 10 // TODO: Why can't we just use the call package to call into the token directly? Answer: this code was built before the call package 11 var AbiStringType goAbi.Type 12 var abiStringArguments goAbi.Arguments 13 14 func init() { 15 var err error 16 AbiStringType, err = goAbi.NewType("string", "", nil) 17 if err != nil { 18 panic(err) 19 } 20 abiStringArguments = goAbi.Arguments{ 21 {Type: AbiStringType}, 22 } 23 } 24 25 // ArticulateString tries to convert hex into a string of printable characters 26 // (ASCII only). If it was successful, then `success` is true. 27 func ArticulateString(hex string) (strResult string, success bool) { 28 if len(hex) < 2 { 29 return "", false 30 } 31 byteValue := base.Hex2Bytes(hex[2:]) 32 return articulateBytes(byteValue) 33 } 34 35 func articulateBytes(byteValue []byte) (strResult string, success bool) { 36 hasPrintableCharacters := false 37 result := make([]byte, 0, len(byteValue)) 38 for _, character := range byteValue { 39 sanitized, replaced := sanitizeByte(character) 40 // if any character has been replaced, it was a special character 41 if replaced > 0 { 42 result = append(result, sanitized...) 43 continue 44 } 45 // if we are here, the character is not a special one, so we need 46 // to check if it's ASCII printable 47 if character >= 20 && character <= 126 { 48 result = append(result, byte(character)) 49 if character > 20 { 50 // ignore space 51 hasPrintableCharacters = true 52 } 53 continue 54 } 55 // The character is not ASCII 56 return "", false 57 } 58 59 if hasPrintableCharacters { 60 return string(result), true 61 } 62 63 return 64 } 65 66 func sanitizeByte(character byte) (replacement []byte, replaced int) { 67 if character == '\\' || character == '\r' { 68 return 69 } 70 if character == '"' { 71 return []byte{'\''}, 1 72 } 73 if character == ',' { 74 // C++ used `|` to replace commas, but it breaks compressed* fields format 75 // (`|` is the delimeter there) 76 return []byte{'_'}, 1 77 } 78 if character == '|' { 79 return []byte{';'}, 1 80 } 81 if character == '\n' { 82 return []byte("[n]"), 1 83 } 84 if character == '\t' { 85 return []byte("[t]"), 1 86 } 87 return []byte{character}, 0 88 } 89 90 func SanitizeString(str string) (sanitized string) { 91 for _, character := range str { 92 sanitizedByte, _ := sanitizeByte(byte(character)) 93 sanitized += string(sanitizedByte) 94 } 95 return 96 } 97 98 // articulateEncodedString translates EVM string into Go string 99 func articulateEncodedString(hexStr string) (result string, err error) { 100 if len(hexStr) < 2 { 101 result = "" 102 return 103 } 104 byteValue := base.Hex2Bytes(hexStr[2:]) 105 unpacked, err := abiStringArguments.Unpack(byteValue) 106 if err != nil { 107 return 108 } 109 result = fmt.Sprint(unpacked[0]) 110 return 111 } 112 113 // articulateBytes32String turns bytes32 encoded string into Go string 114 func articulateBytes32String(hexStr string) (result string) { 115 if len(hexStr) < 2 { 116 return 117 } 118 input := base.Hex2Bytes(hexStr[2:]) 119 if len(input) == 0 { 120 return "" 121 } 122 // Filter out invalid names, four-byte collisions (0x8406d0897da43a33912995c6ffd792f1f2125cd4) 123 if input[0] == 0 { 124 return "" 125 } 126 padStart := len(input) 127 for i := (len(input) - 1); i >= 0; i-- { 128 if input[i] != 0 { 129 break 130 } 131 padStart = i 132 } 133 134 byteValue := input[0:padStart] 135 result, _ = articulateBytes(byteValue) 136 137 return 138 } 139 140 // ArticulateStringOrBytes tries to read string from either EVM string 141 // value or bytes32 hex 142 func ArticulateStringOrBytes(hexStr string) (string, error) { 143 if len(hexStr) < 2 { 144 return "", nil 145 } 146 if len(hexStr[2:]) > 64 { 147 return articulateEncodedString(hexStr) 148 } 149 150 return articulateBytes32String(hexStr), nil 151 }