Jelajahi Sumber

Encode addreses in base58 for print (#1272)

Lucas Steuernagel 2 tahun lalu
induk
melakukan
e9269dac20
16 mengubah file dengan 113 tambahan dan 13 penghapusan
  1. 39 13
      src/emit/strings.rs
  2. TEMPAT SAMPAH
      stdlib/bpf/bigint.bc
  3. TEMPAT SAMPAH
      stdlib/bpf/format.bc
  4. TEMPAT SAMPAH
      stdlib/bpf/heap.bc
  5. TEMPAT SAMPAH
      stdlib/bpf/ripemd160.bc
  6. TEMPAT SAMPAH
      stdlib/bpf/solana.bc
  7. TEMPAT SAMPAH
      stdlib/bpf/stdlib.bc
  8. 30 0
      stdlib/format.c
  9. TEMPAT SAMPAH
      stdlib/test
  10. TEMPAT SAMPAH
      stdlib/wasm/bigint.bc
  11. TEMPAT SAMPAH
      stdlib/wasm/format.bc
  12. TEMPAT SAMPAH
      stdlib/wasm/heap.bc
  13. TEMPAT SAMPAH
      stdlib/wasm/ripemd160.bc
  14. TEMPAT SAMPAH
      stdlib/wasm/stdlib.bc
  15. 43 0
      tests/solana_tests/base58_encoding.rs
  16. 1 0
      tests/solana_tests/mod.rs

+ 39 - 13
src/emit/strings.rs

@@ -5,6 +5,7 @@ use crate::emit::binary::Binary;
 use crate::emit::expression::expression;
 use crate::emit::{TargetRuntime, Variable};
 use crate::sema::ast::{FormatArg, Namespace, RetrieveType, StringLocation, Type};
+use crate::Target;
 use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
 use inkwell::IntPredicate;
 use std::collections::HashMap;
@@ -37,10 +38,14 @@ pub(super) fn format_string<'a, T: TargetRuntime<'a> + ?Sized>(
                 // bool: "true" or "false"
                 Type::Bool => bin.context.i32_type().const_int(5, false),
                 // hex encode bytes
-                Type::Contract(_) | Type::Address(_) => bin
-                    .context
-                    .i32_type()
-                    .const_int(ns.address_length as u64 * 2, false),
+                Type::Contract(_) | Type::Address(_) => {
+                    let len = if ns.target == Target::Solana && *spec != FormatArg::Hex {
+                        base58_size(ns.address_length)
+                    } else {
+                        2 * ns.address_length
+                    };
+                    bin.context.i32_type().const_int(len as u64, false)
+                }
                 Type::Bytes(size) => bin.context.i32_type().const_int(size as u64 * 2, false),
                 Type::String => {
                     let val = expression(target, bin, arg, vartab, function, ns);
@@ -183,9 +188,8 @@ pub(super) fn format_string<'a, T: TargetRuntime<'a> + ?Sized>(
                     };
                 }
                 Type::Address(_) | Type::Contract(_) => {
-                    // for Solana/Substrate, we should encode in base58
+                    // FIXME: For substrate we should encode in the SS58 format
                     let buf = bin.build_alloca(function, bin.address_type(ns), "address");
-
                     bin.builder.build_store(buf, val.into_array_value());
 
                     let len = bin
@@ -193,17 +197,35 @@ pub(super) fn format_string<'a, T: TargetRuntime<'a> + ?Sized>(
                         .i32_type()
                         .const_int(ns.address_length as u64, false);
 
-                    bin.builder.build_call(
-                        bin.module.get_function("hex_encode").unwrap(),
-                        &[output.into(), buf.into(), len.into()],
-                        "",
-                    );
+                    let written_len = if ns.target == Target::Solana && *spec != FormatArg::Hex {
+                        let calculated_len = base58_size(ns.address_length);
+                        let base58_len = bin
+                            .context
+                            .i32_type()
+                            .const_int(calculated_len as u64, false);
+                        bin.builder.build_call(
+                            bin.module
+                                .get_function("base58_encode_solana_address")
+                                .unwrap(),
+                            &[buf.into(), len.into(), output.into(), base58_len.into()],
+                            "",
+                        );
+                        base58_len
+                    } else {
+                        bin.builder.build_call(
+                            bin.module.get_function("hex_encode").unwrap(),
+                            &[output.into(), buf.into(), len.into()],
+                            "",
+                        );
 
-                    let hex_len = bin.builder.build_int_add(len, len, "hex_len");
+                        bin.context
+                            .i32_type()
+                            .const_int(2 * ns.address_length as u64, false)
+                    };
 
                     output = unsafe {
                         bin.builder
-                            .build_gep(bin.context.i8_type(), output, &[hex_len], "")
+                            .build_gep(bin.context.i8_type(), output, &[written_len], "")
                     };
                 }
                 Type::Bytes(size) => {
@@ -550,3 +572,7 @@ pub(super) fn string_location<'a, T: TargetRuntime<'a> + ?Sized>(
         }
     }
 }
+
+fn base58_size(length: usize) -> usize {
+    length * 138 / 100
+}

TEMPAT SAMPAH
stdlib/bpf/bigint.bc


TEMPAT SAMPAH
stdlib/bpf/format.bc


TEMPAT SAMPAH
stdlib/bpf/heap.bc


TEMPAT SAMPAH
stdlib/bpf/ripemd160.bc


TEMPAT SAMPAH
stdlib/bpf/solana.bc


TEMPAT SAMPAH
stdlib/bpf/stdlib.bc


+ 30 - 0
stdlib/format.c

@@ -239,3 +239,33 @@ uint256dec(char *output, uint256_t *val256)
 
     return output;
 }
+
+static const char b58digits[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
+
+// https://github.com/bitcoin/libbase58/blob/b1dd03fa8d1be4be076bb6152325c6b5cf64f678/base58.c inspired this code.
+void base58_encode_solana_address(uint8_t * data, uint32_t data_len, uint8_t * output, uint32_t output_len)
+{
+    uint32_t j, carry, zero_count = 0;
+
+    while (zero_count < data_len && !data[zero_count])
+        ++zero_count;
+
+    for (uint32_t i=zero_count, high = output_len - 1; i < data_len; i++, high = j)
+    {
+        for (carry = data[i], j = output_len - 1; (j > high) || carry; --j)
+        {
+            carry += 256 * output[j];
+            output[j] = carry % 58;
+            carry /= 58;
+            if (!j)
+            {
+                break;
+            }
+        }
+    }
+
+    for (j=0; j < output_len; j++)
+    {
+        output[j] = b58digits[output[j]];
+    }
+}

TEMPAT SAMPAH
stdlib/test


TEMPAT SAMPAH
stdlib/wasm/bigint.bc


TEMPAT SAMPAH
stdlib/wasm/format.bc


TEMPAT SAMPAH
stdlib/wasm/heap.bc


TEMPAT SAMPAH
stdlib/wasm/ripemd160.bc


TEMPAT SAMPAH
stdlib/wasm/stdlib.bc


+ 43 - 0
tests/solana_tests/base58_encoding.rs

@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::borsh_encoding::BorshToken;
+use crate::{account_new, build_solidity};
+use base58::ToBase58;
+
+#[test]
+fn print_addresses() {
+    let mut vm = build_solidity(
+        r#"
+contract Base58 {
+    function print_this(address addr) pure public {
+        print("{}".format(addr));
+    }
+
+    function print_as_hex(address addr) pure public {
+        print("{:x}".format(addr));
+    }
+}
+        "#,
+    );
+
+    vm.constructor(&[]);
+
+    for _ in 0..10 {
+        let account = account_new();
+        let _ = vm.function("print_this", &[BorshToken::Address(account)]);
+        let mut base_58 = account.to_base58();
+        while base_58.len() < 44 {
+            // Rust's to_base58() ignores leading zeros in the byte array,
+            // so it won't transform them into ones. On the other hand, Solana addresses
+            // can start with leading ones: 11128aXFh5abEooZ2ouNDjPjk2TqDaHjG6JkX74vK4q is
+            // a valid address.
+            base_58.insert(0, '1');
+        }
+        assert_eq!(vm.logs, base_58);
+        vm.logs.clear();
+        let _ = vm.function("print_as_hex", &[BorshToken::Address(account)]);
+        let decoded = hex::decode(vm.logs.as_str()).unwrap();
+        assert_eq!(account, decoded.as_ref());
+        vm.logs.clear();
+    }
+}

+ 1 - 0
tests/solana_tests/mod.rs

@@ -7,6 +7,7 @@ mod accessor;
 mod account_info;
 mod arrays;
 mod balance;
+mod base58_encoding;
 mod builtin;
 mod call;
 mod constant;