1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT

use crate::machine::NetworkConfig;

/// Execution level memory tracking and adjustment.
pub trait MemoryLimiter: Sized {
    /// Get a snapshot of the total memory required by the callstack (in bytes). This currently
    /// includes:
    ///
    /// - Memory used by tables (8 bytes per element).
    /// - Memory used by wasmtime instances.
    ///
    /// In the future, this will likely be extended to include IPLD blocks, actor code, etc.
    fn memory_used(&self) -> usize;

    /// Returns `true` if growing by `delta` bytes is allowed. Implement this memory to track and
    /// limit memory usage.
    fn grow_memory(&mut self, delta: usize) -> bool;

    /// Push a new frame onto the call stack, and keep tallying up the current execution memory,
    /// then restore it to the current value when the frame is finished.
    fn with_stack_frame<T, G, F, R>(t: &mut T, g: G, f: F) -> R
    where
        G: Fn(&mut T) -> &mut Self,
        F: FnOnce(&mut T) -> R;

    /// Grows an instance's memory from `from` to `to`. There's no need to manually implement this
    /// unless you need to track instance metrics.
    fn grow_instance_memory(&mut self, from: usize, to: usize) -> bool {
        self.grow_memory(to.saturating_sub(from))
    }

    /// Grows an instance's table from `from` to `to` elements. There's no need to manually
    /// implement this unless you need to track table metrics.
    fn grow_instance_table(&mut self, from: u32, to: u32) -> bool {
        // we charge 8 bytes per table element
        self.grow_memory(to.saturating_sub(from).saturating_mul(8) as usize)
    }
}

/// Limit resources throughout the whole message execution,
/// across all Wasm instances.
pub struct DefaultMemoryLimiter {
    max_memory_bytes: usize,
    curr_memory_bytes: usize,
}

impl DefaultMemoryLimiter {
    pub fn new(max_memory_bytes: usize) -> Self {
        Self {
            max_memory_bytes,
            curr_memory_bytes: 0,
        }
    }

    pub fn for_network(config: &NetworkConfig) -> Self {
        Self::new(config.max_memory_bytes as usize)
    }
}

impl MemoryLimiter for DefaultMemoryLimiter {
    fn memory_used(&self) -> usize {
        self.curr_memory_bytes
    }

    fn grow_memory(&mut self, bytes: usize) -> bool {
        let total_desired = self.curr_memory_bytes.saturating_add(bytes);

        if total_desired > self.max_memory_bytes {
            return false;
        }

        self.curr_memory_bytes = total_desired;
        true
    }

    fn with_stack_frame<T, G, F, R>(t: &mut T, g: G, f: F) -> R
    where
        G: Fn(&mut T) -> &mut Self,
        F: FnOnce(&mut T) -> R,
    {
        let memory_bytes = g(t).curr_memory_bytes;
        let ret = f(t);
        // This method is part of the trait so that a setter like this
        // doesn't have to be made public.
        g(t).curr_memory_bytes = memory_bytes;
        ret
    }
}

#[cfg(test)]
mod tests {
    use super::DefaultMemoryLimiter;
    use crate::machine::limiter::MemoryLimiter;

    #[test]
    fn basics() {
        let mut limits = DefaultMemoryLimiter::new(4);
        assert!(limits.grow_memory(3));
        assert!(limits.grow_memory(1)); // Ok, just at memory limit.
        assert!(!limits.grow_memory(1)); // Fail, over memory limit.

        let mut limits = DefaultMemoryLimiter::new(6);
        assert!(limits.grow_memory(1));
        DefaultMemoryLimiter::with_stack_frame(
            &mut limits,
            |x| x,
            |limits| {
                assert!(limits.grow_memory(3)); // Ok, within memory limit.
                DefaultMemoryLimiter::with_stack_frame(
                    limits,
                    |x| x,
                    |limits| {
                        assert!(!limits.grow_memory(3)); // Fail, 1+3+3 would be over the limit of 6.
                        assert!(limits.grow_memory(2)); // Ok, just at the call stack limit (although we should used a seen a push as well.)
                        assert_eq!(limits.memory_used(), 1 + 3 + 2);
                    },
                );
                assert_eq!(limits.memory_used(), 4);
            },
        );
        assert_eq!(limits.memory_used(), 1);
    }

    #[test]
    fn table() {
        let mut limits = DefaultMemoryLimiter::new(10);
        assert!(limits.grow_instance_table(0, 1)); // 8 bytes
        assert!(limits.grow_memory(2)); // 2 bytes
        assert!(!limits.grow_memory(1));
    }
}