From 02ee78b8027303ce5aa9a43836809ee73d3b9319 Mon Sep 17 00:00:00 2001 From: beer-1 <147697694+beer-1@users.noreply.github.com> Date: Thu, 2 May 2024 15:48:54 +0900 Subject: [PATCH] fix: protect module account from freeze and burn (#33) * protect module account from freeze and burn * add test * fmt --- crates/natives/src/account.rs | 86 +++++++++++++++++- crates/natives/src/staking.rs | 6 +- precompile/binaries/minlib/account.mv | Bin 1140 -> 1239 bytes precompile/binaries/minlib/fungible_asset.mv | Bin 5958 -> 6240 bytes precompile/binaries/stdlib/account.mv | Bin 1143 -> 1249 bytes precompile/binaries/stdlib/fungible_asset.mv | Bin 5958 -> 6240 bytes .../initia_stdlib/sources/account.move | 59 ++++++++++-- .../sources/fa/fungible_asset.move | 63 ++++++++++++- .../minitia_stdlib/sources/account.move | 59 ++++++++++-- .../sources/fa/fungible_asset.move | 63 ++++++++++++- 10 files changed, 305 insertions(+), 31 deletions(-) diff --git a/crates/natives/src/account.rs b/crates/natives/src/account.rs index c487552f..3001b722 100644 --- a/crates/natives/src/account.rs +++ b/crates/natives/src/account.rs @@ -44,6 +44,16 @@ pub struct NativeAccountContext<'a> { api: &'a dyn AccountAPI, new_accounts: BTreeMap, next_account_number: u64, + + #[cfg(feature = "testing")] + test_accounts: BTreeMap< + AccountAddress, + ( + u64, /* account_number */ + u64, /* sequence */ + u8, /* account_type */ + ), + >, } impl<'a> NativeAccountContext<'a> { @@ -52,6 +62,9 @@ impl<'a> NativeAccountContext<'a> { api, new_accounts: Default::default(), next_account_number, + + #[cfg(feature = "testing")] + test_accounts: Default::default(), } } @@ -63,6 +76,18 @@ impl<'a> NativeAccountContext<'a> { .collect::>(), ) } + + #[cfg(feature = "testing")] + pub fn set_account_info( + &mut self, + addr: AccountAddress, + account_number: u64, + sequence: u64, + account_type: u8, + ) { + self.test_accounts + .insert(addr, (account_number, sequence, account_type)); + } } /*************************************************************************************************** @@ -94,6 +119,18 @@ fn native_get_account_info( if let Some(new_account) = account_context.new_accounts.get(&address) { (true, new_account.0, 0, new_account.1) } else { + #[cfg(feature = "testing")] + if let Some((account_number, sequence, account_type)) = + account_context.test_accounts.get(&address) + { + return Ok(smallvec![ + Value::bool(true), + Value::u64(*account_number), + Value::u64(*sequence), + Value::u8(*account_type) + ]); + } + account_context .api .get_account_info(address) @@ -135,7 +172,7 @@ fn native_create_account( .create_account; debug_assert!(ty_args.is_empty()); - debug_assert!(arguments.len() == 2); + debug_assert!(arguments.len() == 3); context.charge(gas_params.base_cost)?; @@ -145,11 +182,17 @@ fn native_create_account( abort_code: UNKNOWN_ACCOUNT_TYPE, }); } + let mut account_number = safely_pop_arg!(arguments, u64); let address = safely_pop_arg!(arguments, AccountAddress); let account_context = context.extensions_mut().get_mut::(); - let account_number = account_context.next_account_number; - account_context.next_account_number += 1; + + // if the account is not specified, use the next account number + if account_number == 0 { + account_number = account_context.next_account_number; + account_context.next_account_number += 1; + } + account_context .new_accounts .insert(address, (account_number, account_type)); @@ -225,16 +268,49 @@ fn native_create_signer( pub fn make_all( builder: &SafeNativeBuilder, ) -> impl Iterator + '_ { - let natives = [ + let mut natives = vec![]; + natives.extend([ ("get_account_info", native_get_account_info as RawSafeNative), ("request_create_account", native_create_account), ("create_address", native_create_address), ("create_signer", native_create_signer), - ]; + ]); + + #[cfg(feature = "testing")] + natives.extend([( + "set_account_info", + native_test_only_set_account_info as RawSafeNative, + )]); builder.make_named_natives(natives) } +#[cfg(feature = "testing")] +fn native_test_only_set_account_info( + context: &mut SafeNativeContext, + ty_args: Vec, + mut arguments: VecDeque, +) -> SafeNativeResult> { + debug_assert!(ty_args.is_empty()); + debug_assert!(arguments.len() == 4); + + let account_type = safely_pop_arg!(arguments, u8); + let sequence = safely_pop_arg!(arguments, u64); + let account_number = safely_pop_arg!(arguments, u64); + let addr = safely_pop_arg!(arguments, AccountAddress); + + let account_context = context.extensions_mut().get_mut::(); + NativeAccountContext::set_account_info( + account_context, + addr, + account_number, + sequence, + account_type, + ); + + Ok(smallvec![]) +} + // ========================================================================================= // Helpers diff --git a/crates/natives/src/staking.rs b/crates/natives/src/staking.rs index 4aaeae58..67c5b40b 100644 --- a/crates/natives/src/staking.rs +++ b/crates/natives/src/staking.rs @@ -157,10 +157,8 @@ fn native_delegate( } #[cfg(feature = "testing")] - if staking_context.share_ratios.contains_key(&validator) { - let ratios = staking_context.share_ratios.get(&validator).unwrap(); - if ratios.contains_key(&metadata) { - let ratio = ratios.get(&metadata).unwrap(); + if let Some(ratios) = staking_context.share_ratios.get(&validator) { + if let Some(ratio) = ratios.get(&metadata) { return Ok(smallvec![Value::u64(amount * ratio.0 / ratio.1)]); } } diff --git a/precompile/binaries/minlib/account.mv b/precompile/binaries/minlib/account.mv index 893dd902e2738ef15be374fc35a17665347c43c2..47bd3df30cd96d5d59a6ae837f21ddb0de9711f2 100644 GIT binary patch delta 441 zcmZ`#yGjH>5bWp7>`ve9uIC|$;;tMV>c@zIh#w=D8W;$d3xXJ!nG1e~iGN^fXkgFY z89G5tbq`%tJ>Ti0T0I_o8laiVRp28loUGu`&hRFu#bA zUZ3u1)z;8OMC>JLj?y*D(aeL4S-L5?0FY$2K_d3Vo-&=4F;fdBz0}D__iBwW$SjAl z{dfjLomYk#jWWqmhAfe~sj;49jWo#ON|zf`G0sfl`s(`C@u)BEz<>HaJL>Cy`ulpg fT_E%Nh0n2GU&wsjNZhc516ldf(oQtcWLpfs+Y}x) delta 348 zcmZvXElvbM5QX2Xs-EuIZf16X352ZJ6@;5`0|W|z!hqlaE3gQXIRO$wg2IuI;5fh@ z0!xCWnzfkMRj>N1pH2In){l#?+yH0^Q#%XiNAdPU(yK0;jV|XudN1X!d6WJ~n>)5GjSEaAsyvk5zx5{25xKAE~BRvdMa3(CIQB@>}`a`kH zo2%=Wrw3X0GYpaB(o)FLhW;cYV(7#Xh-L3i5f!NKl@99fqAX%V)+j5>q1#eY7)EDh t=nAusa3(k-|4vY!(D)V8p`EgwE^P6qW8%si`R#6v)3e2`J$+gc!7s^g9kBoa diff --git a/precompile/binaries/minlib/fungible_asset.mv b/precompile/binaries/minlib/fungible_asset.mv index 4d67222d355ea24b4013149c8f99b2f804fb6663..b3eb856199b5a0ec42bedb4708bf65734d0dee19 100644 GIT binary patch delta 1909 zcmZ8iOK%%h6u!@S-SNCU9>OUr z$$}jc>SiQ1UGWQ8X^=^W$ACv>HGz@Q5VGL0VeG);g73jQt03m9EsQYB5yS%(Bc$e4 z#6p;u0YVofM+HI=DiZLz8O0hZahT~*6>xhsKriS9xMd-2A2Y#T%*e@pOWJ|d4%*VM z$i;_T2cfE$MJV!ogoeFV4xy1WVw45I8p{U=)ugGjE*Os&AZkLcf2jzelilD=m1MqY z)&n-~h56YF5}1=w^9+N#kj5^ijLV^%yi$h1r9SB{Uv$8(^h3JUftK{IGO%pj3lQ0$ z(9KP+g4D(kMdYR&zUeWTzQyubqqidkD{PC65lv&;^$KEl>UqT8$^(VG4Q%ob82;{5 zfDyktJ%bUuCj;?&pbozehBuWtKtJe}5j&fbeLn(3*}V*j_O3t|>ecbcsE$`BM#|Bp z(RyvV=0|2UacOpPZgReMc`~YIs}u7gx>6gjO)M@?R-$T@iB_YD<PY zHFu8P|A!^^csvtQS2l@J-Iou|?X zd4tu3g_&^XzHpZtq7?(^A%&q}8&sRFAFHxn9UgwQ-l(@Czk$W45$v5si9!bvJ zLL)SgLwy6syv#J7k*iZsCvc2SYEU-_L03`2Ou|S|LEIsi5e4c}n2^y#UiV5yR+Mac z{oRu$u?S>P;5xN`y2nSkK<-qMc(c@YXOi!|F6y{(@|#ztmOGMI*=ef4`&xE|dhq@t zJ5(B}5L>QN1dRwZ4lIpV{&hzGWTV(f-V z%P+S;No)%gYq4b#gSZP!Kq-qR<{90@0+T$1=(7$`uq0GD-c{)O1rps33trHp-F0d5f@5ipiPZd&hyi&}-KqF*UHK#-4yid5p+U zQQA%2Cnfj}b6rscoCQ!=jI*Q%q$z-av|+_De9|k+qb>k`oJ&5+b+?u(vdMgi&vOA5s2$WZnO`r5*hPk6dO<2&l%VbbGgW3kpOILg{C>L@Jqs=J* z2RZ<(BB`Vj0t0&DuEgC;;ks(NOBLv*9x}KBs^xahtvsM)2>#CaQu1ZcmxHD=hot8x t4_Az}&(C*3&qArw{c4)L0EhWicz+zS8z delta 1562 zcmYLJJ!~UI6n^vOXMbjG@2+?Km&CD?*pAnU?Og0+6X)j$+;JQS2OJ#S9dHfMT}|m0 zAt4akQ2~&DEN^msNjtqi(=ls`QFdG_q~~YH2kJNeYpIa zRsi5J$YcHnzxy+aFL^5e=6|5|i~7j1|1C7sC;p$z{~-K0?|#vE+xx8ft(W_KyGCIQ z5I6{spunO$HZ6yiOG}%yHb9P6p7sS=KIs8{Lj}BOF_gl>q^700yd#q*DGvQB}&98?gU{7-n{6^RT^~@#*_N>|7 z3fBww>#|aY&DWg+k?S)5G7u+vzv|%Mu($rx7gh|)X8Kp z9PAt&kJ`y*l1omLoy+If@e0d66-FtH0we9|slvkMlY3A7if9K@$Y zU+0-{7m!|LHy5XU--RG)Oof!L24;CduS3X|XXP!=&SS7`#VrxbWo&*$ix z>|UTgp{}r^mgvjz1tyHAB+qh7Oyt=GS~RGdqu1ok1&TpG+Y4{8)$HAHvP30t7gN+c z$v$2DlO2c1BmqF$8H;L|HHPo$GGU1a^@2HPcB!C;g< zC?3@F!CpsW;&|NUzKmQpZi|{+)tp&{U_bl1*yOpOm;GAYYDTILk;amS0>oHZ zl*=NlX8*+X+XZydWvNenbC-oy8ferk6j)ohIuX*_A{La=Gz)6HnufScM=C-fWj*D) z5v~v*c|brhjj{)&rRh-z>js=OT<{h3oXV!KWzNhqq|%5r6{Vc`G_2A^aYZ5vcC3Y= zH{@xItELFzyDPC_Qe7Mxi7joP5KD z1e3l)tuUH&*%Q>Upq4fk9jgdi<}}}R=ROtkp~lIpitSk)C6}lOPSToC(y?@K)S>pA zhM=2g(ICx5p+V3c!4Ca{3+>e%cNe6h1PpeD3W_+7PHu*E%|5r60 kU-%Tm_{&Y|GieUO#IkYvNxTNHb=4at1=>P@FrLZrA5m8wVE_OC delta 217 zcmZvXAr8Vo6h!}@|F^qkH!TfmNT8xsh+YJTLLfK+k~^R0KlVkQ-xo5jekZ*XTRqq~&-w`>tM(FN(%?dp|AOUr z$$}jc>SiQ1UGWQ8X^=^W$ACv>HGz@Q5VGL0VeG);g73jQt03m9EsQYB5yS%(Bc$e4 z#6p;u0YVofM+HI=DiZLz8O0hZahT~*6>xhsKriS9xMd-2A2Y#T%*e@pOWJ|d4%*VM z$i;_T2cfE$MJV!ogoeFV4xy1WVw45I8p{U=)ugGjE*Os&AZkLcf2jzelilD=m1MqY z)&n-~h56YF5}1=w^9+N#kj5^ijLV^%yi$h1r9SB{Uv$8(^h3JUftK{IGO%pj3lQ0$ z(9KP+g4D(kMdYR&zUeWTzQyubqqidkD{PC65lv&;^$KEl>UqT8$^(VG4Q%ob82;{5 zfDyktJ%bUuCj;?&pbozehBuWtKtJe}5j&fbeLn(3*}V*j_O3t|>ecbcsE$`BM#|Bp z(RyvV=0|2UacOpPZgReMc`~YIs}u7gx>6gjO)M@?R-$T@iB_YD<PY zHFu8P|A!^^csvtQS2l@J-Iou|?X zd4tu3g_&^XzHpZtq7?(^A%&q}8&sRFAFHxn9UgwQ-l(@Czk$W45$v5si9!bvJ zLL)SgLwy6syv#J7k*iZsCvc2SYEU-_L03`2Ou|S|LEIsi5e4c}n2^y#UiV5yR+Mac z{oRu$u?S>P;5xN`y2nSkK<-qMc(c@YXOi!|F6y{(@|#ztmOGMI*=ef4`&xE|dhq@t zJ5(B}5L>QN1dRwZ4lIpV{&hzGWTV(f-V z%P+S;No)%gYq4b#gSZP!Kq-qR<{90@0+T$1=(7$`uq0GD-c{)O1rps33trHp-F0d5f@5ipiPZd&hyi&}-KqF*UHK#-4yid5p+U zQQA%2Cnfj}b6rscoCQ!=jI*Q%q$z-av|+_De9|k+qb>k`oJ&5+b+?u(vdMgi&vOA5s2$WZnO`r5*hPk6dO<2&l%VbbGgW3kpOILg{C>L@Jqs=J* z2RZ<(BB`Vj0t0&DuEgC;;ks(NOBLv*9x}KBs^xahtvsM)2>#CaQu1ZcmxHD=hot8x t4_Az}&(C*3&qArw{c4)L0EhWicz+zS8z delta 1562 zcmYLJJ!~UI6n^vOXMbjG@2+?Km&CD?*pAnU?Og0+6X)j$+;JQS2OJ#S9dHfMT}|m0 zAt4akQ2~&DEN^msNjtqi(=ls`QFdG_q~~YH2kJNeYpIa zRsi5J$YcHnzxy+aFL^5e=6|5|i~7j1|1C7sC;p$z{~-K0?|#vE+xx8ft(W_KyGCIQ z5I6{spunO$HZ6yiOG}%yHb9P6p7sS=KIs8{Lj}BOF_gl>q^700yd#q*DGvQB}&98?gU{7-n{6^RT^~@#*_N>|7 z3fBww>#|aY&DWg+k?S)5G7u+vzv|%Mu($rx7gh|)X8Kp z9PAt&kJ`y*l1omLoy+If@e0d66-FtH0we9|slvkMlY3A7if9K@$Y zU+0-{7m!|LHy5XU--RG)Oof!L24;CduS3X|XXP!=&SS7`#VrxbWo&*$ix z>|UTgp{}r^mgvjz1tyHAB+qh7Oyt=GS~RGdqu1ok1&TpG+Y4{8)$HAHvP30t7gN+c z$v$2DlO2c1BmqF$8H;L|HHPo$GGU1a^@2HPcB!C;g< zC?3@F!CpsW;&|NUzKmQpZi|{+)tp&{U_bl1*yOpOm;GAYYDTILk;amS0>oHZ zl*=NlX8*+X+XZydWvNenbC-oy8ferk6j)ohIuX*_A{La=Gz)6HnufScM=C-fWj*D) z5v~v*c|brhjj{)&rRh-z>js=OT<{h3oXV!KWzNhqq|%5r6{Vc`G_2A^aYZ5vcC3Y= zH{@xItELFzyDPC_Qe7Mxi7joP5KD z1e3l)tuUH&*%Q>Upq4fk9jgdi<}}}R=ROtkp~lIpitSk)C6}lOPSToC(y?@K)S>pA zhM=2g(ICx5p+V3c!4Ca{): address; native public(friend) fun create_signer(addr: address): signer; + #[test_only] + native public fun set_account_info(addr: address, account_number: u64, sequence: u64, account_type: u8); + #[test_only] /// Create signer for testing public fun create_signer_for_test(addr: address): signer { create_signer(addr) } @@ -174,4 +182,35 @@ module initia_std::account { let authentication_key = bcs::to_bytes(&new_address); assert!(vector::length(&authentication_key) == 32, 0); } + + #[test(new_address = @0x41, new_address2 = @0x42, new_address3 = @0x43, new_address4 = @0x44)] + public fun test_create_table_account_and_object_account( + new_address: address, new_address2: address, new_address3: address, new_address4: address, + ) { + let table_account_num = create_table_account(new_address); + assert!(table_account_num == get_account_number(new_address), 0); + assert!(is_table_account(new_address), 1); + assert!(exists_at(new_address), 2); + + // set base account with 0 sequence + set_account_info(new_address2, 100, 0, ACCOUNT_TYPE_BASE); + let table_account_num = create_table_account(new_address2); + assert!(table_account_num == get_account_number(new_address2), 0); + assert!(table_account_num == 100, 0); + assert!(is_table_account(new_address2), 1); + assert!(exists_at(new_address2), 2); + + let object_account_num = create_object_account(new_address3); + assert!(object_account_num == get_account_number(new_address3), 3); + assert!(is_object_account(new_address3), 4); + assert!(exists_at(new_address3), 5); + + // set base account with 0 sequence + set_account_info(new_address4, 200, 0, ACCOUNT_TYPE_BASE); + let object_account_num = create_object_account(new_address4); + assert!(object_account_num == get_account_number(new_address4), 0); + assert!(object_account_num == 200, 0); + assert!(is_object_account(new_address4), 1); + assert!(exists_at(new_address4), 2); + } } diff --git a/precompile/modules/initia_stdlib/sources/fa/fungible_asset.move b/precompile/modules/initia_stdlib/sources/fa/fungible_asset.move index 2da3907b..e8e30b5f 100644 --- a/precompile/modules/initia_stdlib/sources/fa/fungible_asset.move +++ b/precompile/modules/initia_stdlib/sources/fa/fungible_asset.move @@ -8,6 +8,7 @@ module initia_std::fungible_asset { use std::option::{Self, Option}; use std::signer; use std::string::{Self, String}; + use std::account; /// The transfer ref and the fungible asset do not match. const ETRANSFER_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 2; @@ -49,6 +50,8 @@ module initia_std::fungible_asset { const ESUPPLY_UNDERFLOW: u64 = 20; /// Supply resource is not found for a metadata object. const ESUPPLY_NOT_FOUND: u64 = 21; + /// Module account store cannot be manipulated. + const ECONNOT_MANIPULATE_MODULE_ACCOUNT_STORE: u64 = 22; // // Constants @@ -440,6 +443,10 @@ module initia_std::fungible_asset { let metadata_addr = object::object_address(ref.metadata); let store_addr = object::object_address(store); + + // cannot freeze module account store + assert!(!is_module_account_store_addr(store_addr), error::invalid_argument(ECONNOT_MANIPULATE_MODULE_ACCOUNT_STORE)); + borrow_global_mut(store_addr).frozen = frozen; // emit event @@ -470,6 +477,10 @@ module initia_std::fungible_asset { assert!(metadata == store_metadata(store), error::invalid_argument(EBURN_REF_AND_STORE_MISMATCH)); let store_addr = object::object_address(store); + + // cannot burn module account funds + assert!(!is_module_account_store_addr(store_addr), error::invalid_argument(ECONNOT_MANIPULATE_MODULE_ACCOUNT_STORE)); + burn(ref, withdraw_internal(store_addr, amount)); } @@ -484,6 +495,10 @@ module initia_std::fungible_asset { error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH), ); + // cannot withdraw module account funds + let store_addr = object::object_address(store); + assert!(!is_module_account_store_addr(store_addr), error::invalid_argument(ECONNOT_MANIPULATE_MODULE_ACCOUNT_STORE)); + withdraw_internal(object::object_address(store), amount) } @@ -610,6 +625,12 @@ module initia_std::fungible_asset { supply.current = supply.current - (amount as u128); } + fun is_module_account_store_addr(store_addr: address): bool { + let fungible_store = object::address_to_object(store_addr); + let owner_addr = object::owner(fungible_store); + account::exists_at(owner_addr) && account::is_module_account(owner_addr) + } + inline fun borrow_fungible_metadata( metadata: Object ): &Metadata acquires Metadata { @@ -628,7 +649,6 @@ module initia_std::fungible_asset { borrow_global(object::object_address(store)) } - #[test_only] struct TestToken has key {} @@ -813,4 +833,45 @@ module initia_std::fungible_asset { amount: _ } = base; } + + #[test(creator = @0xcafe, module_acc = @0x123)] + #[expected_failure(abort_code = 0x10016, location = Self)] + fun test_freeze_module_account_store(creator: &signer, module_acc: &signer) acquires FungibleStore { + let (mint_ref, transfer_ref, _burn_ref, _) = create_fungible_asset(creator); + let metadata = mint_ref.metadata; + + let module_acc_store = create_test_store(module_acc, metadata); + account::set_account_info(signer::address_of(module_acc), 10, 0, 3); + + set_frozen_flag(&transfer_ref, module_acc_store, true); + } + + #[test(creator = @0xcafe, module_acc = @0x123)] + #[expected_failure(abort_code = 0x10016, location = Self)] + fun test_burn_module_account_funds(creator: &signer, module_acc: &signer) acquires FungibleStore, Supply { + let (mint_ref, _transfer_ref, burn_ref, _) = create_fungible_asset(creator); + let metadata = mint_ref.metadata; + + let module_acc_store = create_test_store(module_acc, metadata); + account::set_account_info(signer::address_of(module_acc), 10, 0, 3); + + let fa = mint(&mint_ref, 100); + deposit(module_acc_store, fa); + burn_from(&burn_ref, module_acc_store, 30); + } + + #[test(creator = @0xcafe, module_acc = @0x123)] + #[expected_failure(abort_code = 0x10016, location = Self)] + fun test_withdraw_module_account_funds_with_ref(creator: &signer, module_acc: &signer) acquires FungibleStore, Supply { + let (mint_ref, transfer_ref, _burn_ref, _) = create_fungible_asset(creator); + let metadata = mint_ref.metadata; + + let module_acc_store = create_test_store(module_acc, metadata); + account::set_account_info(signer::address_of(module_acc), 10, 0, 3); + + let fa = mint(&mint_ref, 100); + deposit(module_acc_store, fa); + let fa = withdraw_with_ref(&transfer_ref, module_acc_store, 30); + deposit(module_acc_store, fa); + } } diff --git a/precompile/modules/minitia_stdlib/sources/account.move b/precompile/modules/minitia_stdlib/sources/account.move index 9e58a6a4..c0b3e802 100644 --- a/precompile/modules/minitia_stdlib/sources/account.move +++ b/precompile/modules/minitia_stdlib/sources/account.move @@ -6,7 +6,6 @@ module minitia_std::account { #[test_only] use std::bcs; - friend minitia_std::coin; friend minitia_std::object; friend minitia_std::table; @@ -28,23 +27,30 @@ module minitia_std::account { let (found, _, _, _) = get_account_info(addr); assert!(!found, error::already_exists(EACCOUNT_ALREADY_EXISTS)); - request_create_account(addr, ACCOUNT_TYPE_BASE) + request_create_account(addr, 0, ACCOUNT_TYPE_BASE) } /// TableAccount is similar to CosmosSDK's ModuleAccount in concept, /// as both cannot have a pubkey, there is no way to use the account externally. public(friend) fun create_table_account(addr: address): u64 { - let (found, _, _, _) = get_account_info(addr); - assert!(!found, error::already_exists(EACCOUNT_ALREADY_EXISTS)); + let (found, account_number, sequence, account_type) = get_account_info(addr); + assert!( + !found || (account_type == ACCOUNT_TYPE_BASE && sequence == 0), + error::already_exists(EACCOUNT_ALREADY_EXISTS), + ); - request_create_account(addr, ACCOUNT_TYPE_TABLE) + request_create_account(addr, account_number, ACCOUNT_TYPE_TABLE) } /// ObjectAccount is similar to CosmosSDK's ModuleAccount in concept, /// as both cannot have a pubkey, there is no way to use the account externally. public(friend) fun create_object_account(addr: address): u64 { - let (found, account_number, _, account_type) = get_account_info(addr); - if (found) { + let (found, account_number, sequence, account_type) = get_account_info(addr); + + // base account with sequence 0 is considered as not created. + if (!found || (account_type == ACCOUNT_TYPE_BASE && sequence == 0)) { + request_create_account(addr, account_number, ACCOUNT_TYPE_OBJECT) + } else { // When an Object is deleted, the ObjectAccount in CosmosSDK is designed // not to be deleted in order to prevent unexpected issues. Therefore, // in this case, the creation of an account is omitted. @@ -55,8 +61,6 @@ module minitia_std::account { } else { abort(error::already_exists(EACCOUNT_ALREADY_EXISTS)) } - } else { - request_create_account(addr, ACCOUNT_TYPE_OBJECT) } } @@ -114,17 +118,21 @@ module minitia_std::account { account_type == ACCOUNT_TYPE_MODULE } - native fun request_create_account(addr: address, account_type: u8): u64; + native fun request_create_account(addr: address, account_number: u64, account_type: u8): u64; native public fun get_account_info(addr: address): (bool /* found */, u64 /* account_number */, u64 /* sequence_number */, u8 /* account_type */); native public(friend) fun create_address(bytes: vector): address; native public(friend) fun create_signer(addr: address): signer; + #[test_only] + native public fun set_account_info(addr: address, account_number: u64, sequence: u64, account_type: u8); + #[test_only] /// Create signer for testing public fun create_signer_for_test(addr: address): signer { create_signer(addr) } #[test] public fun test_create_account() { + // base account let bob = create_address(x"0000000000000000000000000000000000000000000000000000000000000b0b"); let carol = create_address(x"00000000000000000000000000000000000000000000000000000000000ca501"); assert!(!exists_at(bob), 0); @@ -173,4 +181,35 @@ module minitia_std::account { let authentication_key = bcs::to_bytes(&new_address); assert!(vector::length(&authentication_key) == 32, 0); } + + #[test(new_address = @0x41, new_address2 = @0x42, new_address3 = @0x43, new_address4 = @0x44)] + public fun test_create_table_account_and_object_account( + new_address: address, new_address2: address, new_address3: address, new_address4: address, + ) { + let table_account_num = create_table_account(new_address); + assert!(table_account_num == get_account_number(new_address), 0); + assert!(is_table_account(new_address), 1); + assert!(exists_at(new_address), 2); + + // set base account with 0 sequence + set_account_info(new_address2, 100, 0, ACCOUNT_TYPE_BASE); + let table_account_num = create_table_account(new_address2); + assert!(table_account_num == get_account_number(new_address2), 0); + assert!(table_account_num == 100, 0); + assert!(is_table_account(new_address2), 1); + assert!(exists_at(new_address2), 2); + + let object_account_num = create_object_account(new_address3); + assert!(object_account_num == get_account_number(new_address3), 3); + assert!(is_object_account(new_address3), 4); + assert!(exists_at(new_address3), 5); + + // set base account with 0 sequence + set_account_info(new_address4, 200, 0, ACCOUNT_TYPE_BASE); + let object_account_num = create_object_account(new_address4); + assert!(object_account_num == get_account_number(new_address4), 0); + assert!(object_account_num == 200, 0); + assert!(is_object_account(new_address4), 1); + assert!(exists_at(new_address4), 2); + } } diff --git a/precompile/modules/minitia_stdlib/sources/fa/fungible_asset.move b/precompile/modules/minitia_stdlib/sources/fa/fungible_asset.move index 9e61c1d4..0098e715 100644 --- a/precompile/modules/minitia_stdlib/sources/fa/fungible_asset.move +++ b/precompile/modules/minitia_stdlib/sources/fa/fungible_asset.move @@ -8,6 +8,7 @@ module minitia_std::fungible_asset { use std::option::{Self, Option}; use std::signer; use std::string::{Self, String}; + use std::account; /// The transfer ref and the fungible asset do not match. const ETRANSFER_REF_AND_FUNGIBLE_ASSET_MISMATCH: u64 = 2; @@ -49,6 +50,8 @@ module minitia_std::fungible_asset { const ESUPPLY_UNDERFLOW: u64 = 20; /// Supply resource is not found for a metadata object. const ESUPPLY_NOT_FOUND: u64 = 21; + /// Module account store cannot be manipulated. + const ECONNOT_MANIPULATE_MODULE_ACCOUNT_STORE: u64 = 22; // // Constants @@ -440,6 +443,10 @@ module minitia_std::fungible_asset { let metadata_addr = object::object_address(ref.metadata); let store_addr = object::object_address(store); + + // cannot freeze module account store + assert!(!is_module_account_store_addr(store_addr), error::invalid_argument(ECONNOT_MANIPULATE_MODULE_ACCOUNT_STORE)); + borrow_global_mut(store_addr).frozen = frozen; // emit event @@ -470,6 +477,10 @@ module minitia_std::fungible_asset { assert!(metadata == store_metadata(store), error::invalid_argument(EBURN_REF_AND_STORE_MISMATCH)); let store_addr = object::object_address(store); + + // cannot burn module account funds + assert!(!is_module_account_store_addr(store_addr), error::invalid_argument(ECONNOT_MANIPULATE_MODULE_ACCOUNT_STORE)); + burn(ref, withdraw_internal(store_addr, amount)); } @@ -484,6 +495,10 @@ module minitia_std::fungible_asset { error::invalid_argument(ETRANSFER_REF_AND_STORE_MISMATCH), ); + // cannot withdraw module account funds + let store_addr = object::object_address(store); + assert!(!is_module_account_store_addr(store_addr), error::invalid_argument(ECONNOT_MANIPULATE_MODULE_ACCOUNT_STORE)); + withdraw_internal(object::object_address(store), amount) } @@ -610,6 +625,12 @@ module minitia_std::fungible_asset { supply.current = supply.current - (amount as u128); } + fun is_module_account_store_addr(store_addr: address): bool { + let fungible_store = object::address_to_object(store_addr); + let owner_addr = object::owner(fungible_store); + account::exists_at(owner_addr) && account::is_module_account(owner_addr) + } + inline fun borrow_fungible_metadata( metadata: Object ): &Metadata acquires Metadata { @@ -628,7 +649,6 @@ module minitia_std::fungible_asset { borrow_global(object::object_address(store)) } - #[test_only] struct TestToken has key {} @@ -813,4 +833,45 @@ module minitia_std::fungible_asset { amount: _ } = base; } + + #[test(creator = @0xcafe, module_acc = @0x123)] + #[expected_failure(abort_code = 0x10016, location = Self)] + fun test_freeze_module_account_store(creator: &signer, module_acc: &signer) acquires FungibleStore { + let (mint_ref, transfer_ref, _burn_ref, _) = create_fungible_asset(creator); + let metadata = mint_ref.metadata; + + let module_acc_store = create_test_store(module_acc, metadata); + account::set_account_info(signer::address_of(module_acc), 10, 0, 3); + + set_frozen_flag(&transfer_ref, module_acc_store, true); + } + + #[test(creator = @0xcafe, module_acc = @0x123)] + #[expected_failure(abort_code = 0x10016, location = Self)] + fun test_burn_module_account_funds(creator: &signer, module_acc: &signer) acquires FungibleStore, Supply { + let (mint_ref, _transfer_ref, burn_ref, _) = create_fungible_asset(creator); + let metadata = mint_ref.metadata; + + let module_acc_store = create_test_store(module_acc, metadata); + account::set_account_info(signer::address_of(module_acc), 10, 0, 3); + + let fa = mint(&mint_ref, 100); + deposit(module_acc_store, fa); + burn_from(&burn_ref, module_acc_store, 30); + } + + #[test(creator = @0xcafe, module_acc = @0x123)] + #[expected_failure(abort_code = 0x10016, location = Self)] + fun test_withdraw_module_account_funds_with_ref(creator: &signer, module_acc: &signer) acquires FungibleStore, Supply { + let (mint_ref, transfer_ref, _burn_ref, _) = create_fungible_asset(creator); + let metadata = mint_ref.metadata; + + let module_acc_store = create_test_store(module_acc, metadata); + account::set_account_info(signer::address_of(module_acc), 10, 0, 3); + + let fa = mint(&mint_ref, 100); + deposit(module_acc_store, fa); + let fa = withdraw_with_ref(&transfer_ref, module_acc_store, 30); + deposit(module_acc_store, fa); + } }