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  }