pallet_revive_uapi/precompiles/utils.rs
1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Helper utilities around pre-compiles.
19//!
20//! This file contains a number of functions for Solidity type encoding
21//! and decoding. Notably these implementations don't require an allocator,
22//! which is why they are here in the first place (e.g. `alloy-core` requires
23//! an allocator for the Solidity `bytes` type).
24
25/// Returns the Solidity selector for `fn_sig`.
26///
27/// Note that this is a const function, it is evaluated at compile time.
28///
29/// # Usage
30///
31/// ```
32/// # use pallet_revive_uapi::solidity_selector;
33/// let sel = solidity_selector("ownCodeHash()");
34/// assert_eq!(sel, [219, 107, 220, 138]);
35/// ```
36pub const fn solidity_selector(fn_sig: &str) -> [u8; 4] {
37 let output: [u8; 32] =
38 const_crypto::sha3::Keccak256::new().update(fn_sig.as_bytes()).finalize();
39 [output[0], output[1], output[2], output[3]]
40}
41
42/// When encoding a Rust `[u8]` to Solidity `bytes`, a small amount
43/// of overhead space is required (for padding and the length word).
44const SOLIDITY_BYTES_ENCODING_OVERHEAD: usize = 64;
45
46/// Encodes a `u32` to big-endian `[u8; 32]` with padded zeros.
47pub fn encode_u32(value: u32) -> [u8; 32] {
48 let mut buf = [0u8; 32];
49 buf[28..].copy_from_slice(&value.to_be_bytes()); // last 4 bytes
50 buf
51}
52
53/// Encodes a `bool` to big-endian `[u8; 32]` with padded zeros.
54pub fn encode_bool(value: bool, out: &mut [u8]) {
55 let mut buf = [0u8; 32];
56 if value {
57 buf[31] = 1;
58 }
59 out[..32].copy_from_slice(&buf[..32]);
60}
61
62/// Encodes the `bytes` argument for the Solidity ABI.
63/// The result is written to `out`.
64///
65/// Returns the number of bytes written.
66///
67/// # Important
68///
69/// This function assumes that the encoded bytes argument follows
70/// two previous other argument that takes up 32 bytes.
71///
72/// So e.g. `function(uint32, bool, bytes)` (with `uint32` and `bool`
73/// being of word size 32 bytes). This assumption is made to calculate
74/// the `offset` word.
75///
76/// # Developer Note
77///
78/// The returned layout will be
79///
80/// ```no_compile
81/// [offset (32 bytes)] [len (32 bytes)] [data (padded to 32)]
82/// ```
83///
84/// The `out` byte array needs to be able to hold (in the worst case)
85/// 95 bytes more than `input.len()`. This is because we write the
86/// following to `out`:
87///
88/// * The offset word → always 32 bytes.
89/// * The length word → always 32 bytes.
90/// * The input itself → exactly `input.len()` bytes.
91/// * We pad the input to a multiple of 32 → between 0 and 31 extra bytes.
92pub fn encode_bytes(input: &[u8], out: &mut [u8]) -> usize {
93 let len = input.len();
94 let padded_len = ((len + 31).div_ceil(32)) * 32;
95
96 // out_len = 32 + padded_len
97 // = 32 + ceil(input_len / 32) * 32
98 assert!(out.len() >= padded_len + SOLIDITY_BYTES_ENCODING_OVERHEAD);
99
100 // Encode offset as a 32-byte big-endian word.
101 // The offset points to the start of the bytes payload in the ABI.
102 //
103 // Important:
104 // This function assumes that the `bytes` argument to the Solidity function follows
105 // two prior argument of word size 32 bytes (e.g. `function(uint32, bool, bytes)`!
106 //
107 // Then the offset will be
108 // * 32 bytes for `uint32`
109 // * 32 bytes for `bool`
110 // * Another 32 bytes for this offset word
111 // The 96 then points to the start of the `bytes` data segment (specifically
112 // its `len` field (`bytes = offset (32 bytes) | len (32 bytes) | data (variable)`).
113 let assumed_offset: u32 = 96;
114 out[28..32].copy_from_slice(&assumed_offset.to_be_bytes()[..4]);
115 out[..28].copy_from_slice(&[0u8; 28]); // make sure the first bytes are zeroed
116
117 // Encode length as a 32-byte big-endian word
118 let mut len_word = [0u8; 32];
119 let len_bytes = (len as u128).to_be_bytes(); // 16 bytes
120 len_word[32 - len_bytes.len()..].copy_from_slice(&len_bytes);
121 out[32..64].copy_from_slice(&len_word);
122
123 // Write data
124 out[64..64 + len].copy_from_slice(input);
125
126 // Zero padding
127 assert!(padded_len >= len);
128 for i in 64 + len..64 + padded_len - len {
129 out[i] = 0;
130 }
131
132 64 + padded_len
133}
134
135/// Simple decoder for a Solidity `bytes` type.
136///
137/// Returns the number of bytes written to `out`.
138pub fn decode_bytes(input: &[u8], out: &mut [u8]) -> usize {
139 let mut buf = [0u8; 4];
140 buf[..].copy_from_slice(&input[28..32]);
141 let offset = u32::from_be_bytes(buf) as usize;
142
143 let mut buf = [0u8; 4];
144 buf[..].copy_from_slice(&input[60..64]);
145 let bytes_len = u32::from_be_bytes(buf) as usize;
146
147 // we start decoding at the start of the payload.
148 // the payload starts at the `len` word here:
149 // `bytes = offset (32 bytes) | len (32 bytes) | data`
150 out[..bytes_len].copy_from_slice(&input[32 + offset..32 + offset + bytes_len]);
151 bytes_len
152}