Packages

Ethereum library for Gleam - JSON-RPC client, transaction signing, ABI encoding, and wallet management on the BEAM

Current section

Files

Jump to
gleeth src gleeth@ethereum@abi@encode.erl
Raw

src/gleeth@ethereum@abi@encode.erl

-module(gleeth@ethereum@abi@encode).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/gleeth/ethereum/abi/encode.gleam").
-export([function_selector/2, encode/1, encode_single/2, encode_call/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
-file("src/gleeth/ethereum/abi/encode.gleam", 28).
?DOC(" Compute the 4-byte function selector: keccak256(name(type1,type2,...))[:4]\n").
-spec function_selector(binary(), list(gleeth@ethereum@abi@types:abi_type())) -> {ok,
bitstring()} |
{error, gleeth@ethereum@abi@types:abi_error()}.
function_selector(Name, Param_types) ->
Sig = <<<<<<Name/binary, "("/utf8>>/binary,
(gleam@string:join(
gleam@list:map(
Param_types,
fun gleeth@ethereum@abi@types:to_string/1
),
<<","/utf8>>
))/binary>>/binary,
")"/utf8>>,
Hash = gleeth@crypto@keccak:keccak256_binary(gleam_stdlib:identity(Sig)),
case gleam_stdlib:bit_array_slice(Hash, 0, 4) of
{ok, Selector} ->
{ok, Selector};
{error, _} ->
{error,
{encode_error, <<"Failed to compute function selector"/utf8>>}}
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 233).
-spec build_bytes(integer(), integer(), bitstring()) -> bitstring().
build_bytes(Value, Remaining, Acc) ->
case Remaining of
0 ->
Acc;
_ ->
Shift = (Remaining - 1) * 8,
Byte = erlang:'band'(erlang:'bsr'(Value, Shift), 16#ff),
build_bytes(Value, Remaining - 1, <<Acc/bitstring, Byte:8>>)
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 228).
?DOC(
" Convert an integer to a 32-byte big-endian representation.\n"
" For values that fit, we use direct byte construction.\n"
).
-spec int_to_bytes32(integer()) -> bitstring().
int_to_bytes32(Value) ->
build_bytes(Value, 32, <<>>).
-file("src/gleeth/ethereum/abi/encode.gleam", 221).
-spec encode_uint256(integer()) -> bitstring().
encode_uint256(Value) ->
int_to_bytes32(Value).
-file("src/gleeth/ethereum/abi/encode.gleam", 117).
-spec encode_uint(integer(), integer()) -> {ok, bitstring()} |
{error, gleeth@ethereum@abi@types:abi_error()}.
encode_uint(Value, Bit_size) ->
Max = erlang:'bsl'(1, Bit_size),
case (Value >= 0) andalso (Value < Max) of
true ->
{ok, encode_uint256(Value)};
false ->
{error,
{encode_error,
<<<<<<"Value out of range for uint"/utf8,
(erlang:integer_to_binary(Bit_size))/binary>>/binary,
": "/utf8>>/binary,
(erlang:integer_to_binary(Value))/binary>>}}
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 131).
-spec encode_int(integer(), integer()) -> {ok, bitstring()} |
{error, gleeth@ethereum@abi@types:abi_error()}.
encode_int(Value, Bit_size) ->
Half = erlang:'bsl'(1, Bit_size - 1),
case (Value >= - Half) andalso (Value < Half) of
true ->
Unsigned = case Value >= 0 of
true ->
Value;
false ->
erlang:'bsl'(1, 256) + Value
end,
{ok, encode_uint256(Unsigned)};
false ->
{error,
{encode_error,
<<<<<<"Value out of range for int"/utf8,
(erlang:integer_to_binary(Bit_size))/binary>>/binary,
": "/utf8>>/binary,
(erlang:integer_to_binary(Value))/binary>>}}
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 245).
-spec bool_to_int(boolean()) -> integer().
bool_to_int(B) ->
case B of
true ->
1;
false ->
0
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 298).
-spec make_zero_bytes_acc(integer(), bitstring()) -> bitstring().
make_zero_bytes_acc(N, Acc) ->
case N =< 0 of
true ->
Acc;
false ->
make_zero_bytes_acc(N - 1, <<Acc/bitstring, 0:8>>)
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 294).
-spec make_zero_bytes(integer()) -> bitstring().
make_zero_bytes(N) ->
make_zero_bytes_acc(N, <<>>).
-file("src/gleeth/ethereum/abi/encode.gleam", 253).
?DOC(" Left-pad data with zeros to 32 bytes.\n").
-spec left_pad32(bitstring()) -> bitstring().
left_pad32(Data) ->
Size = erlang:byte_size(Data),
case Size >= 32 of
true ->
Data;
false ->
Padding = make_zero_bytes(32 - Size),
gleam_stdlib:bit_array_concat([Padding, Data])
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 152).
-spec encode_address(binary()) -> {ok, bitstring()} |
{error, gleeth@ethereum@abi@types:abi_error()}.
encode_address(Addr) ->
Clean = gleeth@utils@hex:strip_prefix(Addr),
case string:length(Clean) of
40 ->
case gleeth@utils@hex:decode(<<"0x"/utf8, Clean/binary>>) of
{ok, Bytes} ->
{ok, left_pad32(Bytes)};
{error, _} ->
{error,
{encode_error,
<<"Invalid hex in address: "/utf8, Addr/binary>>}}
end;
_ ->
{error,
{encode_error,
<<"Address must be 20 bytes (40 hex chars)"/utf8>>}}
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 265).
?DOC(" Right-pad data with zeros to 32 bytes.\n").
-spec right_pad32(bitstring()) -> bitstring().
right_pad32(Data) ->
Size = erlang:byte_size(Data),
case Size >= 32 of
true ->
Data;
false ->
Padding = make_zero_bytes(32 - Size),
gleam_stdlib:bit_array_concat([Data, Padding])
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 165).
-spec encode_fixed_bytes(bitstring(), integer()) -> {ok, bitstring()} |
{error, gleeth@ethereum@abi@types:abi_error()}.
encode_fixed_bytes(Data, Size) ->
case erlang:byte_size(Data) =:= Size of
true ->
{ok, right_pad32(Data)};
false ->
{error,
{encode_error,
<<<<<<"Expected "/utf8,
(erlang:integer_to_binary(Size))/binary>>/binary,
" bytes, got "/utf8>>/binary,
(erlang:integer_to_binary(erlang:byte_size(Data)))/binary>>}}
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 277).
?DOC(" Right-pad data to next 32-byte boundary.\n").
-spec right_pad_to_32(bitstring()) -> bitstring().
right_pad_to_32(Data) ->
Size = erlang:byte_size(Data),
case Size of
0 ->
<<>>;
_ ->
Remainder = Size rem 32,
case Remainder of
0 ->
Data;
_ ->
Padding = make_zero_bytes(32 - Remainder),
gleam_stdlib:bit_array_concat([Data, Padding])
end
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 178).
-spec encode_dynamic_bytes(bitstring()) -> bitstring().
encode_dynamic_bytes(Data) ->
Length = erlang:byte_size(Data),
Length_slot = encode_uint256(Length),
Padded_data = right_pad_to_32(Data),
gleam_stdlib:bit_array_concat([Length_slot, Padded_data]).
-file("src/gleeth/ethereum/abi/encode.gleam", 185).
-spec encode_dynamic_array(
gleeth@ethereum@abi@types:abi_type(),
list(gleeth@ethereum@abi@types:abi_value())
) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.
encode_dynamic_array(Element_type, Elements) ->
Count = erlang:length(Elements),
Count_slot = encode_uint256(Count),
Pairs = gleam@list:map(Elements, fun(V) -> {Element_type, V} end),
gleam@result:'try'(
encode_tuple_values(Pairs),
fun(Encoded) ->
{ok, gleam_stdlib:bit_array_concat([Count_slot, Encoded])}
end
).
-file("src/gleeth/ethereum/abi/encode.gleam", 60).
?DOC(" Encode values using ABI tuple encoding (head/tail layout).\n").
-spec encode_tuple_values(
list({gleeth@ethereum@abi@types:abi_type(),
gleeth@ethereum@abi@types:abi_value()})
) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.
encode_tuple_values(Pairs) ->
Total_head_size = gleam@list:fold(
Pairs,
0,
fun(Acc, Pair) ->
Acc + gleeth@ethereum@abi@types:head_size(erlang:element(1, Pair))
end
),
gleam@result:'try'(
gleam@list:try_fold(
Pairs,
{<<>>, <<>>},
fun(Acc@1, Pair@1) ->
{Head_acc, Tail_acc} = Acc@1,
{T, V} = Pair@1,
case gleeth@ethereum@abi@types:is_dynamic(T) of
false ->
gleam@result:'try'(
encode_value(T, V),
fun(Encoded) ->
{ok,
{gleam_stdlib:bit_array_concat(
[Head_acc, Encoded]
),
Tail_acc}}
end
);
true ->
gleam@result:'try'(
encode_value(T, V),
fun(Encoded@1) ->
Offset = Total_head_size + erlang:byte_size(
Tail_acc
),
Offset_bytes = encode_uint256(Offset),
{ok,
{gleam_stdlib:bit_array_concat(
[Head_acc, Offset_bytes]
),
gleam_stdlib:bit_array_concat(
[Tail_acc, Encoded@1]
)}}
end
)
end
end
),
fun(_use0) ->
{Head, Tail} = _use0,
{ok, gleam_stdlib:bit_array_concat([Head, Tail])}
end
).
-file("src/gleeth/ethereum/abi/encode.gleam", 95).
-spec encode_value(
gleeth@ethereum@abi@types:abi_type(),
gleeth@ethereum@abi@types:abi_value()
) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.
encode_value(T, V) ->
case {T, V} of
{{uint, Size}, {uint_value, N}} ->
encode_uint(N, Size);
{{int, Size@1}, {int_value, N@1}} ->
encode_int(N@1, Size@1);
{address, {address_value, Addr}} ->
encode_address(Addr);
{bool, {bool_value, B}} ->
{ok, encode_uint256(bool_to_int(B))};
{{fixed_bytes, Size@2}, {fixed_bytes_value, Data}} ->
encode_fixed_bytes(Data, Size@2);
{bytes, {bytes_value, Data@1}} ->
{ok, encode_dynamic_bytes(Data@1)};
{string, {string_value, S}} ->
{ok, encode_dynamic_bytes(gleam_stdlib:identity(S))};
{{array, Element_type}, {array_value, Elements}} ->
encode_dynamic_array(Element_type, Elements);
{{fixed_array, Element_type@1, _}, {array_value, Elements@1}} ->
encode_fixed_array(Element_type@1, Elements@1);
{{tuple, Element_types}, {tuple_value, Values}} ->
encode_tuple_type(Element_types, Values);
{_, _} ->
{error,
{encode_error,
<<"Type/value mismatch: "/utf8,
(gleeth@ethereum@abi@types:to_string(T))/binary>>}}
end.
-file("src/gleeth/ethereum/abi/encode.gleam", 15).
?DOC(
" Encode a list of typed values according to the Solidity ABI spec.\n"
" This is the top-level encoding for function parameters.\n"
).
-spec encode(
list({gleeth@ethereum@abi@types:abi_type(),
gleeth@ethereum@abi@types:abi_value()})
) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.
encode(Values) ->
encode_tuple_values(Values).
-file("src/gleeth/ethereum/abi/encode.gleam", 20).
?DOC(" Encode a single typed value.\n").
-spec encode_single(
gleeth@ethereum@abi@types:abi_type(),
gleeth@ethereum@abi@types:abi_value()
) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.
encode_single(Type_, Value) ->
encode_value(Type_, Value).
-file("src/gleeth/ethereum/abi/encode.gleam", 45).
?DOC(" Build complete call data: 4-byte selector + ABI-encoded parameters.\n").
-spec encode_call(
binary(),
list({gleeth@ethereum@abi@types:abi_type(),
gleeth@ethereum@abi@types:abi_value()})
) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.
encode_call(Name, Params) ->
Param_types = gleam@list:map(Params, fun(P) -> erlang:element(1, P) end),
gleam@result:'try'(
function_selector(Name, Param_types),
fun(Selector) ->
gleam@result:'try'(
encode(Params),
fun(Encoded) ->
{ok, gleam_stdlib:bit_array_concat([Selector, Encoded])}
end
)
end
).
-file("src/gleeth/ethereum/abi/encode.gleam", 196).
-spec encode_fixed_array(
gleeth@ethereum@abi@types:abi_type(),
list(gleeth@ethereum@abi@types:abi_value())
) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.
encode_fixed_array(Element_type, Elements) ->
Pairs = gleam@list:map(Elements, fun(V) -> {Element_type, V} end),
encode_tuple_values(Pairs).
-file("src/gleeth/ethereum/abi/encode.gleam", 204).
-spec encode_tuple_type(
list(gleeth@ethereum@abi@types:abi_type()),
list(gleeth@ethereum@abi@types:abi_value())
) -> {ok, bitstring()} | {error, gleeth@ethereum@abi@types:abi_error()}.
encode_tuple_type(Element_types, Values) ->
case erlang:length(Element_types) =:= erlang:length(Values) of
false ->
{error, {encode_error, <<"Tuple element count mismatch"/utf8>>}};
true ->
Pairs = gleam@list:zip(Element_types, Values),
encode_tuple_values(Pairs)
end.