referrerpolicy=no-referrer-when-downgrade

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}