Skip to content

Commit 59f9447

Browse files
[mono-move] Storage slot natives
Implement borrow_storage_slot_resource and borrow_storage_slot_resource_mut, which borrow StorageSlotResource<T> from global storage at the address held in the StorageSlot<T> argument. Add NativeContext::borrow_resource, factoring the shared copy-on-write borrow path out of table_borrow, and add a differential test that runs on both VMs. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent fe5d7cb commit 59f9447

6 files changed

Lines changed: 257 additions & 40 deletions

File tree

third_party/move/mono-move/core/src/native/context.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,17 @@ pub trait NativeContext {
188188
ty: InternedType,
189189
) -> Result<bool, VMInternalError>;
190190

191+
/// Borrows the resource of type `ty` at `address`, returning a reference to
192+
/// it, or `None` if the resource does not exist. A mutable borrow first
193+
/// deep-copies an external or stale value into the local heap
194+
/// (copy-on-write).
195+
fn borrow_resource(
196+
&self,
197+
address: AccountAddress,
198+
mutable: bool,
199+
ty: InternedType,
200+
) -> Result<Option<Ref<'_, Opaque>>, VMInternalError>;
201+
191202
/// BCS-serializes the by-value argument `i` of type `ty` (e.g. a table key).
192203
fn bcs_serialize_arg(&self, i: usize, ty: InternedType) -> Result<Vec<u8>, VMInternalError>;
193204

third_party/move/mono-move/core/src/native/value.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,15 @@ impl Ref<'_, TableHandle> {
184184
}
185185
}
186186

187+
impl Ref<'_, AccountAddress> {
188+
/// Borrows the referenced address.
189+
pub fn get(&self) -> &AccountAddress {
190+
// SAFETY: `AccountAddress` is `[u8; N]` (align 1), so the bytes the
191+
// reference points at reinterpret as `&AccountAddress`.
192+
unsafe { &*(self.ptr() as *const AccountAddress) }
193+
}
194+
}
195+
187196
/// Marker for a type that is not statically known.
188197
///
189198
/// This can be used to build composite types in generic native functions — e.g. the

third_party/move/mono-move/natives/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ pub mod object;
2626
pub mod secp256k1;
2727
pub mod signer;
2828
pub mod state_storage;
29+
pub mod storage_slot;
2930
pub mod string;
3031
pub mod table;
3132
pub mod test_natives;
@@ -45,6 +46,7 @@ pub use object::{make_all_object_natives, ObjectContextExtension};
4546
pub use secp256k1::make_all_secp256k1_natives;
4647
pub use signer::make_all_signer_natives;
4748
pub use state_storage::{make_all_state_storage_natives, StorageUsageAtEpochBoundary};
49+
pub use storage_slot::make_all_storage_slot_natives;
4850
pub use string::make_all_string_natives;
4951
pub use table::make_all_table_natives;
5052
pub use test_natives::{make_all_test_natives, native_u64_add, native_u64_identity};
@@ -96,6 +98,7 @@ pub fn make_all_production_natives<F: NativeContextFamily>() -> Vec<NativeEntry<
9698
natives.extend(make_all_from_bytes_natives::<F>());
9799
natives.extend(make_all_table_natives::<F>());
98100
natives.extend(make_all_secp256k1_natives::<F>());
101+
natives.extend(make_all_storage_slot_natives::<F>());
99102
natives
100103
}
101104

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Aptos Foundation
2+
// Licensed pursuant to the Innovation-Enabling Source Code License, available at https://github.com/aptos-labs/aptos-core/blob/main/LICENSE
3+
4+
//! Natives for the `storage_slot` module.
5+
//!
6+
//! These borrow a `StorageSlotResource<T>` from global storage at the address
7+
//! held in the `StorageSlot<T>` argument. The resource flows through the same
8+
//! read-write set as any other resource, so no storage-slot-specific extension
9+
//! is needed.
10+
11+
use crate::{polymorphic_natives, NativeEntry};
12+
use mono_move_core::native::{
13+
NativeContext, NativeContextFamily, NativeStatus, Ref, VMInternalError,
14+
};
15+
use move_core_types::account_address::AccountAddress;
16+
17+
/// `StorageSlotResource<T>` not found at the slot's address.
18+
const ESTORAGE_SLOT_NOT_FOUND: u64 = 2;
19+
20+
/// Borrows `StorageSlotResource<T>` from global storage at the address held in
21+
/// the `StorageSlot<T>` argument, writing the reference into return slot 0, or
22+
/// aborts if the resource is missing.
23+
//
24+
// TODO(metering): charge gas.
25+
fn borrow_storage_slot_resource<C: NativeContext>(
26+
ctx: &C,
27+
mutable: bool,
28+
) -> Result<NativeStatus, VMInternalError> {
29+
// SAFETY: arg 0 is `&[mut] StorageSlot<T>`, whose single `addr: address`
30+
// field gives it the same representation as `&address`.
31+
let slot: Ref<AccountAddress> = unsafe { ctx.arg(0)? };
32+
let addr = *slot.get();
33+
// ty_arg 1 is `StorageSlotResource<T>` -- the resource to borrow.
34+
let resource_ty = ctx.ty_arg(1)?;
35+
match ctx.borrow_resource(addr, mutable, resource_ty)? {
36+
// SAFETY: return 0 is the `&[mut] StorageSlotResource<T>` reference.
37+
Some(r) => unsafe { ctx.set_return(0, r) }.map(|()| NativeStatus::Success),
38+
None => Ok(NativeStatus::Abort {
39+
code: ESTORAGE_SLOT_NOT_FOUND,
40+
message: Some(format!("StorageSlotResource at address {} not found", addr)),
41+
}),
42+
}
43+
}
44+
45+
/// `0x1::storage_slot::borrow_storage_slot_resource<T, BR>(self: &StorageSlot<T>): &BR`
46+
pub fn native_borrow_storage_slot_resource<C: NativeContext>(
47+
ctx: &C,
48+
) -> Result<NativeStatus, VMInternalError> {
49+
borrow_storage_slot_resource(ctx, false)
50+
}
51+
52+
/// `0x1::storage_slot::borrow_storage_slot_resource_mut<T, BR>(self: &mut StorageSlot<T>): &mut BR`
53+
pub fn native_borrow_storage_slot_resource_mut<C: NativeContext>(
54+
ctx: &C,
55+
) -> Result<NativeStatus, VMInternalError> {
56+
borrow_storage_slot_resource(ctx, true)
57+
}
58+
59+
/// Natives for the `storage_slot` module.
60+
pub fn make_all_storage_slot_natives<F: NativeContextFamily>() -> Vec<NativeEntry<F>> {
61+
polymorphic_natives![
62+
(
63+
"0x1::storage_slot::borrow_storage_slot_resource",
64+
native_borrow_storage_slot_resource
65+
),
66+
(
67+
"0x1::storage_slot::borrow_storage_slot_resource_mut",
68+
native_borrow_storage_slot_resource_mut
69+
),
70+
]
71+
}

third_party/move/mono-move/runtime/src/native_context.rs

Lines changed: 62 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,57 @@ impl<'a> ProductionNativeContext<'a> {
119119
returns_started: Cell::new(false),
120120
}
121121
}
122+
123+
/// Borrows the global-storage value at `storage_key`, rooting a reference to
124+
/// it for the rest of the call, or `None` if the value does not exist. A
125+
/// mutable borrow of an external or stale value first deep-copies it into
126+
/// the local heap (copy-on-write).
127+
fn borrow_storage_value(
128+
&self,
129+
storage_key: InMemoryStorageKey,
130+
mutable: bool,
131+
) -> Result<Option<Ref<'_, Opaque>>, VMInternalError> {
132+
// SAFETY: heap and rws are distinct fields (see the aliasing rule).
133+
let rws = unsafe { &mut **self.rws.get() };
134+
let ptr = if mutable {
135+
match rws.try_borrow_global_mut(self.resource_provider, &storage_key) {
136+
Ok(EntryPtr::Writable(ptr)) => ptr,
137+
Ok(EntryPtr::NonWritable(ptr)) => {
138+
// Copy-on-write: an external or stale value must be copied
139+
// into the local heap before it can be mutated.
140+
let heap = unsafe { &mut **self.heap.get() };
141+
// SAFETY: `ptr` is a live object (provider- or older-epoch-owned).
142+
let copied = unsafe {
143+
deep_copy_or_gc(
144+
heap,
145+
self.desc_provider,
146+
rws,
147+
&self.pool,
148+
self.extensions,
149+
self.frame_ptr,
150+
TopFrame::Native(self.abi),
151+
ptr,
152+
)
153+
}
154+
.map_err(VMInternalError::from)?;
155+
rws.commit_borrow_global_mut(&storage_key, copied);
156+
copied
157+
},
158+
Err(RuntimeError::ResourceDoesNotExist { .. }) => return Ok(None),
159+
Err(e) => return Err(e.into()),
160+
}
161+
} else {
162+
match rws.borrow_global(self.resource_provider, &storage_key) {
163+
Ok(ptr) => ptr,
164+
Err(RuntimeError::ResourceDoesNotExist { .. }) => return Ok(None),
165+
Err(e) => return Err(e.into()),
166+
}
167+
};
168+
// SAFETY: `ptr` is the live value; the reference points at its start, so
169+
// the offset is 0. The pool roots it for the rest of the call.
170+
let handle = unsafe { self.pool.root_reference(ptr.as_ptr(), 0) };
171+
Ok(Some(Ref::from_handle(handle)))
172+
}
122173
}
123174

124175
impl NativeContext for ProductionNativeContext<'_> {
@@ -434,6 +485,16 @@ impl NativeContext for ProductionNativeContext<'_> {
434485
Ok(rws.exists(self.resource_provider, &key)?)
435486
}
436487

488+
fn borrow_resource(
489+
&self,
490+
address: AccountAddress,
491+
mutable: bool,
492+
ty: InternedType,
493+
) -> Result<Option<Ref<'_, Opaque>>, VMInternalError> {
494+
let storage_key = InMemoryStorageKey::resource(address, ty);
495+
self.borrow_storage_value(storage_key, mutable)
496+
}
497+
437498
fn bcs_serialize_arg(&self, i: usize, ty: InternedType) -> Result<Vec<u8>, VMInternalError> {
438499
let slot = self.abi.args().get(i).copied().ok_or_else(|| {
439500
VMInternalError::invariant_violation(format!("arg index {i} out of bounds"))
@@ -514,46 +575,7 @@ impl NativeContext for ProductionNativeContext<'_> {
514575
value_ty: InternedType,
515576
) -> Result<Option<Ref<'_, Opaque>>, VMInternalError> {
516577
let storage_key = InMemoryStorageKey::table_item(*handle, key.into(), value_ty);
517-
// SAFETY: heap and rws are distinct fields (see the aliasing rule).
518-
let rws = unsafe { &mut **self.rws.get() };
519-
let ptr = if mutable {
520-
match rws.try_borrow_global_mut(self.resource_provider, &storage_key) {
521-
Ok(EntryPtr::Writable(ptr)) => ptr,
522-
Ok(EntryPtr::NonWritable(ptr)) => {
523-
// Copy-on-write: an external or stale value must be copied
524-
// into the local heap before it can be mutated.
525-
let heap = unsafe { &mut **self.heap.get() };
526-
// SAFETY: `ptr` is a live object (provider- or older-epoch-owned).
527-
let copied = unsafe {
528-
deep_copy_or_gc(
529-
heap,
530-
self.desc_provider,
531-
rws,
532-
&self.pool,
533-
self.extensions,
534-
self.frame_ptr,
535-
TopFrame::Native(self.abi),
536-
ptr,
537-
)
538-
}
539-
.map_err(VMInternalError::from)?;
540-
rws.commit_borrow_global_mut(&storage_key, copied);
541-
copied
542-
},
543-
Err(RuntimeError::ResourceDoesNotExist { .. }) => return Ok(None),
544-
Err(e) => return Err(e.into()),
545-
}
546-
} else {
547-
match rws.borrow_global(self.resource_provider, &storage_key) {
548-
Ok(ptr) => ptr,
549-
Err(RuntimeError::ResourceDoesNotExist { .. }) => return Ok(None),
550-
Err(e) => return Err(e.into()),
551-
}
552-
};
553-
// SAFETY: `ptr` is the live entry value; the reference points at its
554-
// start, so the offset is 0. The pool roots it for the rest of the call.
555-
let handle = unsafe { self.pool.root_reference(ptr.as_ptr(), 0) };
556-
Ok(Some(Ref::from_handle(handle)))
578+
self.borrow_storage_value(storage_key, mutable)
557579
}
558580

559581
// TODO(cleanup): See if there's a way to separate out argument-reading from boxing.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// RUN: publish
2+
module 0x1::storage_slot {
3+
struct StorageSlotResource<T> has key {
4+
val: T,
5+
}
6+
7+
struct StorageSlot<phantom T> has store {
8+
addr: address,
9+
}
10+
11+
native fun borrow_storage_slot_resource<T: store, BR>(self: &StorageSlot<T>): &BR;
12+
native fun borrow_storage_slot_resource_mut<T: store, BR>(self: &mut StorageSlot<T>): &mut BR;
13+
14+
// Publishes the backing resource under `account` and returns a slot at
15+
// `addr`; the caller passes an `addr` that matches `account`.
16+
public fun new_at<T: store>(account: &signer, addr: address, value: T): StorageSlot<T> {
17+
move_to(account, StorageSlotResource<T> { val: value });
18+
StorageSlot { addr }
19+
}
20+
21+
// A slot pointing at `addr` with no backing resource, to exercise the
22+
// missing-resource abort.
23+
public fun unbacked<T: store>(addr: address): StorageSlot<T> {
24+
StorageSlot { addr }
25+
}
26+
27+
public fun borrow<T: store>(self: &StorageSlot<T>): &T {
28+
&borrow_storage_slot_resource<T, StorageSlotResource<T>>(self).val
29+
}
30+
31+
public fun borrow_mut<T: store>(self: &mut StorageSlot<T>): &mut T {
32+
&mut borrow_storage_slot_resource_mut<T, StorageSlotResource<T>>(self).val
33+
}
34+
35+
public fun destroy<T: store>(self: StorageSlot<T>): T {
36+
let StorageSlot { addr } = self;
37+
let StorageSlotResource { val } = move_from<StorageSlotResource<T>>(addr);
38+
val
39+
}
40+
}
41+
42+
module 0x42::main {
43+
use 0x1::storage_slot;
44+
45+
// borrow reads back the published value.
46+
public fun borrow_reads(s: signer, a: address): u64 {
47+
let slot = storage_slot::new_at<u64>(&s, a, 42);
48+
let v = *storage_slot::borrow(&slot);
49+
storage_slot::destroy(slot);
50+
v
51+
}
52+
53+
// borrow_mut writes through the reference; a later borrow sees the update.
54+
public fun borrow_mut_updates(s: signer, a: address): u64 {
55+
let slot = storage_slot::new_at<u64>(&s, a, 10);
56+
let before = *storage_slot::borrow(&slot);
57+
*storage_slot::borrow_mut(&mut slot) = before + 5;
58+
let after = *storage_slot::borrow(&slot);
59+
storage_slot::destroy(slot);
60+
after
61+
}
62+
63+
// A heap-boxed value type (vector<u8>) round-trips through the borrow.
64+
public fun borrow_vector(s: signer, a: address): vector<u8> {
65+
let slot = storage_slot::new_at<vector<u8>>(&s, a, b"hello");
66+
let v = *storage_slot::borrow(&slot);
67+
storage_slot::destroy(slot);
68+
v
69+
}
70+
71+
// borrow on a slot with no backing resource aborts.
72+
public fun borrow_missing_aborts(a: address): u64 {
73+
let slot = storage_slot::unbacked<u64>(a);
74+
let v = *storage_slot::borrow(&slot);
75+
storage_slot::destroy(slot);
76+
v
77+
}
78+
79+
// borrow_mut on a slot with no backing resource aborts.
80+
public fun borrow_mut_missing_aborts(a: address): u64 {
81+
let slot = storage_slot::unbacked<u64>(a);
82+
*storage_slot::borrow_mut(&mut slot) = 0;
83+
storage_slot::destroy(slot);
84+
0
85+
}
86+
}
87+
88+
// RUN: execute 0x42::main::borrow_reads --args 0x42, 0x42
89+
// CHECK: results: 42
90+
91+
// RUN: execute 0x42::main::borrow_mut_updates --args 0x7, 0x7
92+
// CHECK: results: 15
93+
94+
// RUN: execute 0x42::main::borrow_vector --args 0x9, 0x9
95+
// CHECK: results: 0x68656c6c6f
96+
97+
// RUN: execute 0x42::main::borrow_missing_aborts --args 0x12
98+
// CHECK-SUBSTR: aborted: code 2
99+
100+
// RUN: execute 0x42::main::borrow_mut_missing_aborts --args 0x13
101+
// CHECK-SUBSTR: aborted: code 2

0 commit comments

Comments
 (0)