Skip to content

Commit 1c8386d

Browse files
valeriyralexsporn
authored andcommitted
fix(iota-framework): audit issue 58 extra mutable self in coin manager (#5447)
* fix(iota-framework): use immutable self in `coin_manager::additional_metadata` * feat(iota-framework-snapshot): the snapshots update * fix(iota-swarm-config): update the baseline * fix(iota-framework): add `coin_manager::get_additional_metadata` * feat(iota-framework-snapshot): the snapshots update * fix(iota-swarm-config): update the baseline
1 parent 96ef973 commit 1c8386d

11 files changed

+134
-63
lines changed

crates/iota-framework-snapshot/manifest.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,5 +38,15 @@
3838
"0x000000000000000000000000000000000000000000000000000000000000000b",
3939
"0x000000000000000000000000000000000000000000000000000000000000107a"
4040
]
41+
},
42+
"5": {
43+
"git_revision": "07139cab4aed",
44+
"package_ids": [
45+
"0x0000000000000000000000000000000000000000000000000000000000000001",
46+
"0x0000000000000000000000000000000000000000000000000000000000000002",
47+
"0x0000000000000000000000000000000000000000000000000000000000000003",
48+
"0x000000000000000000000000000000000000000000000000000000000000000b",
49+
"0x000000000000000000000000000000000000000000000000000000000000107a"
50+
]
4151
}
4252
}

crates/iota-framework/packages/iota-framework/sources/coin_manager.move

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,85 +17,88 @@ module iota::coin_manager {
1717
use iota::balance::{Balance, Supply};
1818
use iota::dynamic_field as df;
1919

20-
/// The error returned when the maximum supply reached
20+
/// The error returned when the maximum supply reached.
2121
const EMaximumSupplyReached: u64 = 0;
2222

23-
/// The error returned if a attempt is made to change the maximum supply after setting it
23+
/// The error returned if an attempt is made to change the maximum supply after setting it.
2424
const EMaximumSupplyAlreadySet: u64 = 1;
2525

26-
/// The error returned if a attempt is made to change the maximum supply that is lower than the total supply
26+
/// The error returned if an attempt is made to change the maximum supply that is lower than the total supply.
2727
const EMaximumSupplyLowerThanTotalSupply: u64 = 2;
2828

29-
/// The error returned if a attempt is made to change the maximum supply that is higher than the maximum possible supply
29+
/// The error returned if an attempt is made to change the maximum supply that is higher than the maximum possible supply.
3030
const EMaximumSupplyHigherThanPossible: u64 = 3;
3131

32-
/// The error returned if you try to edit nonexisting additional metadata
32+
/// The error returned if you try to edit nonexisting additional metadata.
3333
const EAdditionalMetadataDoesNotExist: u64 = 4;
3434

35-
/// The maximum supply supported by `CoinManager`
35+
/// The maximum supply supported by `CoinManager`.
3636
const MAX_SUPPLY: u64 = 18_446_744_073_709_551_614u64;
3737

38-
/// Holds all related objects to a Coin in a convenient shared function
38+
/// The name of the related additional metadata dynamic field.
39+
const ADDITIONAL_METADATA_NAME: vector<u8> = b"additional_metadata";
40+
41+
/// Holds all the related objects to the coin of type `T` in a convenient shared function.
3942
public struct CoinManager<phantom T> has key, store {
4043
id: UID,
41-
/// The original TreasuryCap object as returned by `create_currency`
44+
/// The original `TreasuryCap` object as returned by `create_currency`.
4245
treasury_cap: TreasuryCap<T>,
43-
/// Metadata object, original one from the `coin` module, if available
46+
/// Metadata object, original one from the `coin` module, if available.
4447
metadata: Option<CoinMetadata<T>>,
45-
/// Immutable Metadata object, only to be used as a last resort if the original metadata is frozen
48+
/// Immutable Metadata object, only to be used as a last resort if the original metadata is frozen.
4649
immutable_metadata: Option<ImmutableCoinMetadata<T>>,
47-
/// Optional maximum supply, if set you can't mint more as this number - can only be set once
50+
/// Optional maximum supply, if set you can't mint more as this number - can only be set once.
4851
maximum_supply: Option<u64>,
49-
/// Flag indicating if the supply is considered immutable (TreasuryCap is exchanged for this)
52+
/// Flag indicating if the supply is considered immutable (TreasuryCap is exchanged for this).
5053
supply_immutable: bool,
51-
/// Flag indicating if the metadata is considered immutable (MetadataCap is exchanged for this)
54+
/// Flag indicating if the metadata is considered immutable (MetadataCap is exchanged for this).
5255
metadata_immutable: bool
5356
}
5457

55-
/// Like `TreasuryCap`, but for dealing with `TreasuryCap` inside `CoinManager` objects
58+
/// Like `TreasuryCap`, but for dealing with `TreasuryCap` inside `CoinManager` objects.
5659
public struct CoinManagerTreasuryCap<phantom T> has key, store {
5760
id: UID
5861
}
5962

60-
/// Metadata has it's own Cap, independent of the `TreasuryCap`
63+
/// Metadata has it's own Cap, independent of the `TreasuryCap`.
6164
public struct CoinManagerMetadataCap<phantom T> has key, store {
6265
id: UID
6366
}
6467

65-
/// The immutable version of CoinMetadata, used in case of migrating from frozen objects
68+
/// The immutable version of `CoinMetadata`, used in case of migrating from frozen objects
6669
/// to a `CoinManager` holding the metadata.
6770
public struct ImmutableCoinMetadata<phantom T> has store {
6871
/// Number of decimal places the coin uses.
69-
/// A coin with `value ` N and `decimals` D should be shown as N / 10^D
72+
/// A coin with `value` N and `decimals` D should be shown as N / 10^D
7073
/// E.g., a coin with `value` 7002 and decimals 3 should be displayed as 7.002
7174
/// This is metadata for display usage only.
7275
decimals: u8,
73-
/// Name for the token
76+
/// Name for the token.
7477
name: string::String,
75-
/// Symbol for the token
78+
/// Symbol for the token.
7679
symbol: ascii::String,
77-
/// Description of the token
80+
/// Description of the token.
7881
description: string::String,
79-
/// URL for the token logo
82+
/// URL for the token logo.
8083
icon_url: Option<Url>
8184
}
8285

83-
/// Event triggered once `Coin` ownership is transferred to a new `CoinManager`
86+
/// Event triggered once `Coin` ownership is transferred to a new `CoinManager`.
8487
public struct CoinManaged has copy, drop {
8588
coin_name: std::ascii::String
8689
}
8790

88-
/// Event triggered if the ownership of the treasury part of a `CoinManager` is renounced
91+
/// Event triggered if the ownership of the treasury part of a `CoinManager` is renounced.
8992
public struct TreasuryOwnershipRenounced has copy, drop {
9093
coin_name: std::ascii::String
9194
}
9295

93-
/// Event triggered if the ownership of the metadata part of a `CoinManager` is renounced
96+
/// Event triggered if the ownership of the metadata part of a `CoinManager` is renounced.
9497
public struct MetadataOwnershipRenounced has copy, drop {
9598
coin_name: std::ascii::String
9699
}
97100

98-
/// Wraps all important objects related to a `Coin` inside a shared object
101+
/// Wraps all important objects related to a `Coin` inside a shared object.
99102
public fun new<T> (
100103
treasury_cap: TreasuryCap<T>,
101104
metadata: CoinMetadata<T>,
@@ -127,7 +130,7 @@ module iota::coin_manager {
127130
)
128131
}
129132

130-
/// This function allows the same as `new` but under the assumption the Metadata can not be transferred
133+
/// This function allows the same as `new` but under the assumption the Metadata can not be transferred.
131134
/// This would typically be the case with `Coin` instances where the metadata is already frozen.
132135
public fun new_with_immutable_metadata<T> (
133136
treasury_cap: TreasuryCap<T>,
@@ -165,7 +168,7 @@ module iota::coin_manager {
165168
)
166169
}
167170

168-
/// Convenience wrapper to create a new `Coin` and instantly wrap the cap inside a `CoinManager`
171+
/// Convenience wrapper to create a new `Coin` and instantly wrap the cap inside a `CoinManager`.
169172
public fun create<T: drop> (
170173
witness: T,
171174
decimals: u8,
@@ -189,35 +192,44 @@ module iota::coin_manager {
189192
new(cap, meta, ctx)
190193
}
191194

192-
// Option to add a additional metadata object to the manager
193-
// Can contain whatever you need in terms of additional metadata as a object
195+
/// Option to add an additional metadata object to the manager.
196+
/// Can contain whatever you need in terms of additional metadata as a object.
194197
public fun add_additional_metadata<T, Value: store>(
195198
_: &CoinManagerMetadataCap<T>,
196199
manager: &mut CoinManager<T>,
197200
value: Value
198201
) {
199-
df::add(&mut manager.id, b"additional_metadata", value);
202+
df::add(&mut manager.id, ADDITIONAL_METADATA_NAME, value);
200203
}
201204

202-
// Option to replace a additional metadata object to the manager
203-
// Can contain whatever you need in terms of additional metadata as a object
205+
/// Option to replace an additional metadata object to the manager.
206+
/// Can contain whatever you need in terms of additional metadata as a object.
204207
public fun replace_additional_metadata<T, Value: store, OldValue: store>(
205208
_: &CoinManagerMetadataCap<T>,
206209
manager: &mut CoinManager<T>,
207210
value: Value
208211
): OldValue {
209-
assert!(df::exists_(&manager.id, b"additional_metadata"), EAdditionalMetadataDoesNotExist);
210-
let old_value = df::remove<vector<u8>, OldValue>(&mut manager.id, b"additional_metadata");
211-
df::add(&mut manager.id, b"additional_metadata", value);
212+
assert!(df::exists_(&manager.id, ADDITIONAL_METADATA_NAME), EAdditionalMetadataDoesNotExist);
213+
let old_value = df::remove<vector<u8>, OldValue>(&mut manager.id, ADDITIONAL_METADATA_NAME);
214+
df::add(&mut manager.id, ADDITIONAL_METADATA_NAME, value);
212215
old_value
213216
}
214217

215-
// Retrieve the additional metadata
218+
#[deprecated(note = b"Use `iota::coin_manager::get_additional_metadata` instead.")]
216219
public fun additional_metadata<T, Value: store>(
217220
manager: &mut CoinManager<T>
218221
): &Value {
219-
assert!(df::exists_(&manager.id, b"additional_metadata"), EAdditionalMetadataDoesNotExist);
220-
let meta: &Value = df::borrow(&manager.id, b"additional_metadata");
222+
assert!(df::exists_(&manager.id, ADDITIONAL_METADATA_NAME), EAdditionalMetadataDoesNotExist);
223+
let meta: &Value = df::borrow(&manager.id, ADDITIONAL_METADATA_NAME);
224+
meta
225+
}
226+
227+
/// Immutably borrows the additional metadata.
228+
public fun get_additional_metadata<T, Value: store>(
229+
manager: &CoinManager<T>
230+
): &Value {
231+
assert!(df::exists_(&manager.id, ADDITIONAL_METADATA_NAME), EAdditionalMetadataDoesNotExist);
232+
let meta: &Value = df::borrow(&manager.id, ADDITIONAL_METADATA_NAME);
221233
meta
222234
}
223235

@@ -234,7 +246,7 @@ module iota::coin_manager {
234246
option::fill(&mut manager.maximum_supply, maximum_supply);
235247
}
236248

237-
/// A irreversible action renouncing supply ownership which can be called if you hold the `CoinManagerTreasuryCap`.
249+
/// An irreversible action renouncing supply ownership which can be called if you hold the `CoinManagerTreasuryCap`.
238250
/// This action provides `Coin` holders with some assurances if called, namely that there will
239251
/// not be any new minting or changes to the supply from this point onward. The maximum supply
240252
/// will be set to the current supply and will not be changed any more afterwards.
@@ -262,7 +274,7 @@ module iota::coin_manager {
262274
});
263275
}
264276

265-
/// A irreversible action renouncing manager ownership which can be called if you hold the `CoinManagerMetadataCap`.
277+
/// An irreversible action renouncing manager ownership which can be called if you hold the `CoinManagerMetadataCap`.
266278
/// This action provides `Coin` holders with some assurances if called, namely that there will
267279
/// not be any changes to the metadata from this point onward.
268280
public fun renounce_metadata_ownership<T>(
@@ -293,33 +305,33 @@ module iota::coin_manager {
293305
manager.metadata_immutable || option::is_some(&manager.immutable_metadata)
294306
}
295307

296-
// Get a read-only version of the metadata, available for everyone
308+
/// Get a read-only version of the metadata, available for everyone.
297309
public fun metadata<T>(manager: &CoinManager<T>): &CoinMetadata<T> {
298310
option::borrow(&manager.metadata)
299311
}
300312

301-
// Get a read-only version of the read-only metadata, available for everyone
313+
/// Get a read-only version of the read-only metadata, available for everyone.
302314
public fun immutable_metadata<T>(manager: &CoinManager<T>): &ImmutableCoinMetadata<T> {
303315
option::borrow(&manager.immutable_metadata)
304316
}
305317

306-
/// Get the total supply as a number
318+
/// Get the total supply as a number.
307319
public fun total_supply<T>(manager: &CoinManager<T>): u64 {
308320
coin::total_supply(&manager.treasury_cap)
309321
}
310322

311323
/// Get the maximum supply possible as a number.
312-
/// If no maximum set it's the maximum u64 possible
324+
/// If no maximum set it's the maximum u64 possible.
313325
public fun maximum_supply<T>(manager: &CoinManager<T>): u64 {
314326
option::get_with_default(&manager.maximum_supply, MAX_SUPPLY)
315327
}
316328

317-
/// Convenience function returning the remaining supply that can be minted still
329+
/// Convenience function returning the remaining supply that can be minted still.
318330
public fun available_supply<T>(manager: &CoinManager<T>): u64 {
319331
maximum_supply(manager) - total_supply(manager)
320332
}
321333

322-
/// Returns if a maximum supply has been set for this Coin or not
334+
/// Returns if a maximum supply has been set for this Coin or not.
323335
public fun has_maximum_supply<T>(manager: &CoinManager<T>): bool {
324336
option::is_some(&manager.maximum_supply)
325337
}
@@ -404,7 +416,7 @@ module iota::coin_manager {
404416
coin::update_description(&manager.treasury_cap, option::borrow_mut(&mut manager.metadata), description)
405417
}
406418

407-
/// Update the `url` of the coin in the `CoinMetadata`
419+
/// Update the `url` of the coin in the `CoinMetadata`.
408420
public fun update_icon_url<T>(
409421
_: &CoinManagerMetadataCap<T>,
410422
manager: &mut CoinManager<T>,

crates/iota-framework/packages/iota-framework/tests/coin_manager_tests.move

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,8 @@ module iota::coin_manager_tests {
347347
transfer::public_share_object(wrapper);
348348
scenario.end();
349349
}
350-
350+
351+
#[allow(deprecated_usage)]
351352
#[test]
352353
fun test_additional_metadata() {
353354
let sender = @0xA;
@@ -394,6 +395,52 @@ module iota::coin_manager_tests {
394395
scenario.end();
395396
}
396397

398+
#[test]
399+
fun test_get_additional_metadata() {
400+
let sender = @0xA;
401+
let mut scenario = test_scenario::begin(sender);
402+
let witness = COIN_MANAGER_TESTS{};
403+
404+
// Create a `Coin`.
405+
let (cap, meta) = coin::create_currency(
406+
witness,
407+
0,
408+
b"TEST",
409+
b"TEST",
410+
b"TEST",
411+
option::none(),
412+
scenario.ctx(),
413+
);
414+
415+
let (cmcap, metacap, mut wrapper) = coin_manager::new(cap, meta, scenario.ctx());
416+
417+
let bonus = BonusMetadata {
418+
website: url::new_unsafe(ascii::string(b"https://example.com")),
419+
is_amazing: false
420+
};
421+
422+
metacap.add_additional_metadata(&mut wrapper, bonus);
423+
424+
assert!(!wrapper.get_additional_metadata<COIN_MANAGER_TESTS, BonusMetadata>().is_amazing);
425+
426+
let bonus2 = BonusMetadata {
427+
website: url::new_unsafe(ascii::string(b"https://iota.org")),
428+
is_amazing: true
429+
};
430+
431+
let oldmeta = metacap.replace_additional_metadata<COIN_MANAGER_TESTS, BonusMetadata, BonusMetadata>(&mut wrapper, bonus2);
432+
433+
let BonusMetadata { website: _, is_amazing: _ } = oldmeta;
434+
435+
assert!(wrapper.get_additional_metadata<COIN_MANAGER_TESTS, BonusMetadata>().is_amazing);
436+
437+
cmcap.renounce_treasury_ownership(&mut wrapper);
438+
metacap.renounce_metadata_ownership(&mut wrapper);
439+
transfer::public_share_object(wrapper);
440+
441+
scenario.end();
442+
}
443+
397444
#[test]
398445
#[expected_failure(abort_code = iota::dynamic_field::EFieldAlreadyExists)]
399446
fun test_double_adding_additional_metadata() {
64 Bytes
Binary file not shown.

crates/iota-framework/published_api.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,6 +2089,9 @@ replace_additional_metadata
20892089
additional_metadata
20902090
public fun
20912091
0x2::coin_manager
2092+
get_additional_metadata
2093+
public fun
2094+
0x2::coin_manager
20922095
enforce_maximum_supply
20932096
public fun
20942097
0x2::coin_manager

0 commit comments

Comments
 (0)