-
Notifications
You must be signed in to change notification settings - Fork 11.7k
[move-std] UID assert workarounds #24145
Description
Overview
In this SIP I mention that one of the benefits of the singleton ability modifier is its ability to reduce the trust on shared, "singleton" objects from third-party packages.
As a (not very realistic) example, let's consider the Clock object. Many packages depend on the Clock object with the trusted assumption that 0x6 is a singleton. If, somehow, a secondary Clock were to be created w/ a different timestamp, this Clock could be used as input to any number of packages in an attempt to change their core behavior. Obviously the Clock object has a deeper level of trust associated with it, but this problem becomes a very real attack vector as you are integrating with non-sui-framework "singleton"s.
The singleton ability modifier would allow us to provably verify that shared object will in fact remain singleton. In the short term, you can also enforce this by asserting the sui::object::id(<some-singleton>) against its known UID which is kept in either a const or a locally defined object.
Problem
This short term solution works fine when you are interacting with the package on its respective network, however it breaks down when you need to build out a testing suite against said singleton: the singleton will derive a unique address when it is created during your test and will not pass the assert. To get around this problem you can maintain a testing only UID and specific network UIDs but this becomes cumbersome.
Solution
I think there are a few ways you can get around this (I am sure there are more). I will briefly explain some ideas and we can continue discussion in this issue. I also think some of these solutions have a much broader application beyond just this specific issue.
1. #[test_only] const overrides
Override const values for use in tests:
const EXECTED_CLOCK_ADDRESS: @0x6;
#[test_only]
const EXECTED_CLOCK_ADDRESS: <known-clock-testing-address>;
or piggy backing off of the new mode feature, if we could specify #[mode(default)] vs #[test_only] consts:
#[mode(default)]
const EXECTED_CLOCK_ADDRESS: @0x6;
#[test_only]
const EXECTED_CLOCK_ADDRESS: <known-clock-testing-address>;
and #[mode(default)] is used when no --mode is specified.
2. Custom #[test_only] UID creation
Allow setting the UID for an object when creating it in tests and / or swapping out the UID of an already created object's UID for a desired one. Both of these options would of course be #[test_only]:
let mut clock = sui::clock::create_for_testing(ctx);
sui::object::set_uid(&mut clock, @0x6);
The former option would need to be exposed through a custom `#[test_only] constructor by the module that defines the singleton:
#[test_only]
public function create_for_testing_with_uid(id: ID): <singleton> {
<singleton> {
id: sui::object::from(id),
...
}
}
3. Bypassing asserts within tests
(1) Allow you to flag an abort code as acceptable and / or (2) add #[test_only] short-circuits to functions:
#[test_only(allow = EInvalidObject)] // <-- Option 1
fun assert_expected<T: key>(object: &T, expected: address) {
#[test_only] return; // <-- Option 2
assert!(object::id_address(&object) == expected, EInvalidObject);
}