init.rs 9.76 KB
Newer Older
1
// Copyright 2018-2021 Parity Technologies (UK) Ltd.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::DynamicAllocator;
16
use crate::traits::{
17
18
19
    pull_spread_root,
    push_spread_root,
};
20
use cfg_if::cfg_if;
21
22
23
24
use core::{
    mem,
    mem::ManuallyDrop,
};
25
26
27
28
29
30
31
32
33
34
35
36
use ink_primitives::Key;

/// The default dynamic allocator key offset.
///
/// This is where the dynamic allocator is stored on the contract storage.
const DYNAMIC_ALLOCATOR_KEY_OFFSET: [u8; 32] = [0xFE; 32];

/// The phase in which a contract execution can be.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ContractPhase {
    /// Initializes the global dynamic storage allocator from scratch.
    ///
37
    /// Upon initialization, it will be created from scratch as if the
38
39
40
41
    /// contract has been deployed for the first time.
    Deploy,
    /// Initializes the global dynamic storage allocator from storage.
    ///
42
    /// Upon initialization, the dynamic storage allocator will be pulled
43
44
45
46
47
48
49
50
51
52
53
    /// from the contract storage with the assumption that a former
    /// contract deployment has already taken place in the past.
    Call,
}

/// The state of the dynamic allocator global instance.
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
enum DynamicAllocatorState {
    /// The global instance has not yet been initialized.
    ///
54
    /// Upon initialization, it will be created from scratch as if the
55
56
57
58
    /// contract has been deployed for the first time.
    UninitDeploy,
    /// The global instance has not yet been initialized.
    ///
59
    /// Upon initialization, it will be pulled from the contract storage
60
61
62
    /// with the assumption that a former contract deployment has already
    /// taken place in the past.
    UninitCall,
63
    /// The global instance has been initialized successfully and can be used.
64
    Initialized(DynamicAllocator),
65
66
    /// The global instance has been finalized and can no longer be used.
    Finalized,
67
68
69
70
71
72
73
74
75
76
77
}

impl From<ContractPhase> for DynamicAllocatorState {
    fn from(phase: ContractPhase) -> Self {
        match phase {
            ContractPhase::Deploy => DynamicAllocatorState::UninitDeploy,
            ContractPhase::Call => DynamicAllocatorState::UninitCall,
        }
    }
}

78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
impl DynamicAllocatorState {
    /// Initializes the global dynamic storage allocator instance.
    ///
    /// The `phase` parameter describes for which execution phase the dynamic
    /// storage allocator needs to be initialized since this is different
    /// in contract instantiations and calls.
    pub fn initialize(&mut self, phase: ContractPhase) {
        match self {
            DynamicAllocatorState::Initialized(_)
                // We only perform this check on Wasm compilation to avoid
                // some overly constrained check for the off-chain testing.
                if cfg!(all(not(feature = "std"), target_arch = "wasm32")) =>
            {
                panic!(
                    "cannot initialize the dynamic storage \
                     allocator instance twice in Wasm",
                )
            }
            DynamicAllocatorState::Finalized => {
                panic!(
                    "cannot initialize the dynamic storage \
                 allocator after it has been finalized",
                )
            }
            state => {
                *state = phase.into();
            }
        }
    }

    /// Finalizes the global instance for the dynamic storage allocator.
    ///
    /// The global dynamic storage allocator must not be used after this!
    pub fn finalize(&mut self) {
        match self {
            DynamicAllocatorState::Initialized(allocator) => {
                // Push all state of the global dynamic storage allocator
                // instance back onto the contract storage.
                push_spread_root::<DynamicAllocator>(
117
                    allocator,
118
                    &Key::from(DYNAMIC_ALLOCATOR_KEY_OFFSET),
119
120
121
122
123
124
125
126
127
128
                );
                // Prevent calling `drop` on the dynamic storage allocator
                // instance since this would clear all contract storage
                // again.
                let _ = ManuallyDrop::new(mem::take(allocator));
                *self = DynamicAllocatorState::Finalized;
            }
            DynamicAllocatorState::Finalized => {
                panic!(
                    "cannot finalize the dynamic storage allocator \
129
                     after it has already been finalized"
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
                )
            }
            DynamicAllocatorState::UninitCall | DynamicAllocatorState::UninitDeploy => {
                // Nothing to do in these states.
            }
        }
    }

    /// Runs the closure on the global instance for the dynamic storage allocator.
    ///
    /// Will automatically initialize the global allocator instance if it has not
    /// yet been initialized.
    ///
    /// # Panics
    ///
    /// If the global dynamic storage allocator instance has already been finalized.
    pub fn on_instance<F, R>(&mut self, f: F) -> R
    where
        F: FnOnce(&mut DynamicAllocator) -> R,
    {
        match self {
            DynamicAllocatorState::UninitDeploy => {
                let mut allocator = DynamicAllocator::default();
                let result = f(&mut allocator);
                *self = DynamicAllocatorState::Initialized(allocator);
                result
            }
            DynamicAllocatorState::UninitCall => {
158
                let mut allocator = pull_spread_root::<DynamicAllocator>(&Key::from(
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
                    DYNAMIC_ALLOCATOR_KEY_OFFSET,
                ));
                let result = f(&mut allocator);
                *self = DynamicAllocatorState::Initialized(allocator);
                result
            }
            DynamicAllocatorState::Initialized(ref mut allocator) => f(allocator),
            DynamicAllocatorState::Finalized => {
                panic!(
                    "cannot operate on the dynamic storage \
                     allocator after it has been finalized"
                );
            }
        }
    }
}

176
177
178
179
180
181
182
cfg_if! {
    if #[cfg(all(not(feature = "std"), target_arch = "wasm32"))] {
        // Procedures for the Wasm compilation:

        /// The global instance for the dynamic storage allocator.
        static mut GLOBAL_INSTANCE: DynamicAllocatorState = DynamicAllocatorState::UninitDeploy;

183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
        /// Forwards to the `initialize` of the global dynamic storage allocator instance.
        pub fn initialize(phase: ContractPhase) {
            // SAFETY: Accessing the global allocator in Wasm mode is single
            //         threaded and will not return back a reference to its
            //         internal state. Also the `initialize` method won't
            //         re-enter the dynamic storage in any possible way.
            unsafe { &mut GLOBAL_INSTANCE }.initialize(phase);
        }

        /// Forwards to the `finalize` of the global dynamic storage allocator instance.
        pub fn finalize() {
            // SAFETY: Accessing the global allocator in Wasm mode is single
            //         threaded and will not return back a reference to its
            //         internal state. Also the `finalize` method won't
            //         re-enter the dynamic storage in any possible way.
            unsafe { &mut GLOBAL_INSTANCE }.finalize();
199
200
        }

201
202
        /// Forwards to the `on_instance` of the global dynamic storage allocator instance.
        pub fn on_instance<F, R>(f: F) -> R
203
204
205
        where
            F: FnOnce(&mut DynamicAllocator) -> R,
        {
206
207
208
209
210
211
212
            // SAFETY: Accessing the global allocator in Wasm mode is single
            //         threaded and will not return back a reference to its
            //         internal state. Also this is an internal API only called
            //         through `alloc` and `free` both of which do not return
            //         anything that could allow to re-enter the dynamic storage
            //         allocator instance.
            unsafe { &mut GLOBAL_INSTANCE }.on_instance(f)
213
214
215
216
217
218
219
220
        }

    } else if #[cfg(feature = "std")] {
        // Procedures for the off-chain environment and testing compilation:

        use ::core::cell::RefCell;
        thread_local!(
            /// The global instance for the dynamic storage allocator.
221
222
223
            static GLOBAL_INSTANCE: RefCell<DynamicAllocatorState> = RefCell::new(
                DynamicAllocatorState::UninitDeploy
            );
224
        );
225
226
227
228
229
230
        /// Forwards to the `initialize` of the global dynamic storage allocator instance.
        pub fn initialize(phase: ContractPhase) {
            GLOBAL_INSTANCE.with(|instance| {
                instance.borrow_mut().initialize(phase)
            });
        }
231

232
233
        /// Forwards to the `finalize` of the global dynamic storage allocator instance.
        pub fn finalize() {
234
            GLOBAL_INSTANCE.with(|instance| {
235
                instance.borrow_mut().finalize()
236
237
238
            });
        }

239
240
        /// Forwards to the `on_instance` of the global dynamic storage allocator instance.
        pub fn on_instance<F, R>(f: F) -> R
241
242
243
244
        where
            F: FnOnce(&mut DynamicAllocator) -> R,
        {
            GLOBAL_INSTANCE.with(|instance| {
245
                instance.borrow_mut().on_instance(f)
246
247
248
249
250
251
252
253
254
            })
        }

    } else {
        compile_error! {
            "ink! only support compilation as `std` or `no_std` + `wasm32-unknown`"
        }
    }
}