From b810effbdb28e330c5d29eaf25e7b05299c418e1 Mon Sep 17 00:00:00 2001 From: beer-1 <147697694+beer-1@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:11:23 +0900 Subject: [PATCH] feat: update fa metadata query to return `INIT-6` (#133) * update fa metadata query to return INIT-6 * add multisig_v2 to minitia_stdlib * fix multisig_v2 * fix multisig_v2 --- precompile/binaries/minlib/multisig_v2.mv | Bin 0 -> 9973 bytes precompile/binaries/stdlib/fungible_asset.mv | Bin 10461 -> 10439 bytes precompile/binaries/stdlib/multisig_v2.mv | Bin 9699 -> 9973 bytes .../sources/fa/fungible_asset.move | 25 +- .../initia_stdlib/sources/multisig_v2.move | 327 ++- .../minitia_stdlib/sources/multisig_v2.move | 2472 +++++++++++++++++ 6 files changed, 2672 insertions(+), 152 deletions(-) create mode 100644 precompile/binaries/minlib/multisig_v2.mv create mode 100644 precompile/modules/minitia_stdlib/sources/multisig_v2.move diff --git a/precompile/binaries/minlib/multisig_v2.mv b/precompile/binaries/minlib/multisig_v2.mv new file mode 100644 index 0000000000000000000000000000000000000000..50094d4a5f8eb1e6f2bfd6005958667d9fa1f4c4 GIT binary patch literal 9973 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 delta 1451 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 T+yOal90bh(!bWue;79)p*&t)5 delta 3037 zcmb_eNsJs<8Gi58d)3wN|LawEPxU_2J-yBJ^z5FkX2vs~89d(NHSrcFjva3?7!qO| z2gsq}07Bw|DOn&CP#GJ7|z68V}RO6 zi-XiYrl{qI)sT>qA89G-m^bV(b#%lN7E?z?6?N>`gktRYmMNwlpPq@d{C*LrHQ#Mr3?l03a_g{i08t80cRv2?x$~br_1uN<8vO9i1fGalwJ-AU_vwZ3Cp{&u z!_`cstzDavZR4rS%Py{KSEGkXBTO=gK@t-{i95p26E}F%C^mH7&OrSBP_5^^b0K?PiV#3wriMA8U zr-mo{!mp-l+=RE&#f(SVH92RcM34~uTynVy&GG*SEGv`uquBtc?ElZOH2xbb&O@+N zLY{360Psl>B+)T)m1{9wlYQC=QW#g|IL09^Cu!Of9?DJ>Gnys5z(*SeZMP)GG)E^$hZl3RJQLoeHXFW`JLCUY7!l-r z%o!`l`OvJ5I%b*n)Es#r6%)e;(Xwdriv<=fn? zt(U;XIyOwsG=g4}#{nFK0T^8Kd>YhYFoQ0bu3R!){g$iWarG*P+Kx^>f~(blY`@3V z`y67tb08TX)T+zopSk+rKLm1ESy{S&lh`E3huk4@d z+#U^jVH8I;TyGol-9)|{7y*+49D`vP+rIR0IlL^?cZB*UhgMezO&r%TPvL}CFp<{d zK_5*sY0BVU7*957!qe&>!thlc1^b#%Z-@tD4c{x;?akvrx443xpeDh=E$)`UhAlW{ zrdIVsLH$LzQ^W|9K?$dE2BuSSoP`;f-Hs2suKp_2--LQgz*JW$PvTtU9UYv5_Dq__ zEPwzgUCS8gU@S<302 zO6q4D#(r+8+ZJry(Lqc7%7TEaUt5tMWZb7F<3o%H1{E$7=h?)0%b-$rP!ppB@*ZEa zF~iBAS2Y}|p1=y~C_#WstDXM-wnqk7wS=SzJqZ#P7fPVT>?8CqR8CBZ`FD!M6! zwz27^Vmj89!RK9@E=oqkXqeGKjO+nA0N_NlEfC`!>CQmo>JqwCfeulAf@`DQc1^Bp z(y_Lb$oe`_kt=|lwtUeEoL#ME~sae3pVABcTWLGF+s12!{ICCT5%A$;mK(qg-d)zgP;jQ z%yP+|4F6Dmk*C9x{R>1(-|RoeOW}w8Yq23sa0RV!zB1+vi$#)r>IdO-l~EG?b|qr4 z-&8IWMeVHCh@vi4r`rni9atcb1-C$zUcjO$Ql)#8P+n)dE^l$SNEBtT7yIZ%*+&#* zVF}Anf^t;mWh%`w0R&!Glpa^#k79S)7I5NkLL5|kcJ zHcb^7y-J!mqDOGlfnlOE8_itM!Z94j31Z47Ou#sdf`PG=#7PQoa#T9Bl_JiYAO^Kl zJQx=A&ybx8@77=BPlpqY>G8Alzc4vr7z!#h0akK37!iFWbZ#1$-$9bzewJ$m*)VJj z#z$q%#1i>nEPSJ}o;kk{c$c6+=}HnT4QvWJ8-n`>`rV40G7f@f0Ks_p;=t$s4Z)mG A+W-In diff --git a/precompile/modules/initia_stdlib/sources/fa/fungible_asset.move b/precompile/modules/initia_stdlib/sources/fa/fungible_asset.move index 49579a31..34179119 100644 --- a/precompile/modules/initia_stdlib/sources/fa/fungible_asset.move +++ b/precompile/modules/initia_stdlib/sources/fa/fungible_asset.move @@ -481,13 +481,7 @@ module initia_std::fungible_asset { #[view] /// Get the symbol of the fungible asset from the `metadata` object. public fun symbol(metadata: Object): String acquires Metadata { - let md = borrow_fungible_metadata(&metadata); - if (object::is_owner(metadata, @initia_std) - && md.symbol == string::utf8(b"uinit")) { - return string::utf8(b"INIT") - }; - - md.symbol + metadata(metadata).symbol } #[view] @@ -505,19 +499,20 @@ module initia_std::fungible_asset { #[view] /// Get the metadata struct from the `metadata` object. public fun metadata(metadata: Object): Metadata acquires Metadata { - *borrow_fungible_metadata(&metadata) + let md = *borrow_fungible_metadata(&metadata); + if (object::is_owner(metadata, @initia_std) + && md.symbol == string::utf8(b"uinit")) { + md.symbol = string::utf8(b"INIT"); + md.decimals = 6; + }; + + md } #[view] /// Get the decimals from the `metadata` object. public fun decimals(metadata: Object): u8 acquires Metadata { - let md = borrow_fungible_metadata(&metadata); - if (object::is_owner(metadata, @initia_std) - && md.symbol == string::utf8(b"uinit")) { - return 6 - }; - - md.decimals + metadata(metadata).decimals } #[view] diff --git a/precompile/modules/initia_stdlib/sources/multisig_v2.move b/precompile/modules/initia_stdlib/sources/multisig_v2.move index 544f6eb5..57dffb75 100644 --- a/precompile/modules/initia_stdlib/sources/multisig_v2.move +++ b/precompile/modules/initia_stdlib/sources/multisig_v2.move @@ -297,6 +297,7 @@ module initia_std::multisig_v2 { assert_member(&members, &signer::address_of(account)); assert_uniqueness(members); assert_tier_config(tiers, tier_weights, &members, member_tiers); + assert_uniqueness(tiers); // check threshold computed from each member weights let total_weight = vector::fold( @@ -384,22 +385,30 @@ module initia_std::multisig_v2 { ) acquires MultisigWallet { assert!( vector::length(&module_address_list) == vector::length(&module_name_list) - && vector::length(&module_name_list) == vector::length(&function_name_list) - && vector::length(&function_name_list) == vector::length(&type_args_list) + && vector::length(&module_name_list) + == vector::length(&function_name_list) + && vector::length(&function_name_list) + == vector::length(&type_args_list) && vector::length(&type_args_list) == vector::length(&args_list), error::invalid_argument(EINVALID_PROPOSAL_MESSAGE_LENGTH) ); - + + let index = 0; let execute_messages = vector::map( module_address_list, |module_address| { - let (_, index) = vector::index_of(&module_address_list, &module_address); + let module_name = *vector::borrow(&module_name_list, index); + let function_name = *vector::borrow(&function_name_list, index); + let type_args = *vector::borrow(&type_args_list, index); + let args = *vector::borrow(&args_list, index); + index = index + 1; + ExecuteMessage { module_address, - module_name: *vector::borrow(&module_name_list, index), - function_name: *vector::borrow(&function_name_list, index), - type_args: *vector::borrow(&type_args_list, index), - args: *vector::borrow(&args_list, index), + module_name, + function_name, + type_args, + args, json_args: vector[] } } @@ -427,23 +436,31 @@ module initia_std::multisig_v2 { ) acquires MultisigWallet { assert!( vector::length(&module_address_list) == vector::length(&module_name_list) - && vector::length(&module_name_list) == vector::length(&function_name_list) - && vector::length(&function_name_list) == vector::length(&type_args_list) + && vector::length(&module_name_list) + == vector::length(&function_name_list) + && vector::length(&function_name_list) + == vector::length(&type_args_list) && vector::length(&type_args_list) == vector::length(&args_list), error::invalid_argument(EINVALID_PROPOSAL_MESSAGE_LENGTH) ); + let index = 0; let execute_messages = vector::map( module_address_list, |module_address| { - let (_, index) = vector::index_of(&module_address_list, &module_address); + let module_name = *vector::borrow(&module_name_list, index); + let function_name = *vector::borrow(&function_name_list, index); + let type_args = *vector::borrow(&type_args_list, index); + let json_args = *vector::borrow(&args_list, index); + index = index + 1; + ExecuteMessage { module_address, - module_name: *vector::borrow(&module_name_list, index), - function_name: *vector::borrow(&function_name_list, index), - type_args: *vector::borrow(&type_args_list, index), + module_name, + function_name, + type_args, args: vector[], - json_args: *vector::borrow(&args_list, index) + json_args } } ); @@ -583,6 +600,7 @@ module initia_std::multisig_v2 { &new_members, new_member_tiers ); + assert_uniqueness(new_tiers); // check threshold computed from each member weights let total_weight = vector::fold( @@ -688,11 +706,12 @@ module initia_std::multisig_v2 { fun construct_members_with_tiers( members: vector
, member_tiers: vector, tiers: vector ): vector { + let index = 0; vector::map( members, |member| { - let (_, index) = vector::index_of(&members, &member); let tier_name = *vector::borrow(&member_tiers, index); + index = index + 1; // find tier with tier_name in tiers let (found, tier_index) = vector::find( @@ -773,12 +792,7 @@ module initia_std::multisig_v2 { ); event::emit( - CreateProposalEvent { - multisig_addr, - proposal_id, - proposer, - execute_messages - } + CreateProposalEvent { multisig_addr, proposal_id, proposer, execute_messages } ) } @@ -917,7 +931,7 @@ module initia_std::multisig_v2 { vector::contains(&tiers, &tier), error::invalid_argument(EINVALID_MEMBER_TIERS) ) - ) + ); } #[test_only] @@ -939,14 +953,17 @@ module initia_std::multisig_v2 { fun create_votes_map(members: vector, votes: vector): SimpleMap { let votes_map = simple_map::create(); + let index = 0; vector::for_each( members, |member| { - let (_, index) = vector::index_of(&members, &member); + let vote = *vector::borrow(&votes, index); + index = index + 1; + simple_map::add( &mut votes_map, member, - *vector::borrow(&votes, index) + vote ) } ); @@ -957,7 +974,9 @@ module initia_std::multisig_v2 { // view functions tests #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] - fun is_exist_test(account1: signer, account2: signer, account3: signer) { + fun is_exist_test( + account1: signer, account2: signer, account3: signer + ) { let addr1 = signer::address_of(&account1); let addr2 = signer::address_of(&account2); let addr3 = signer::address_of(&account3); @@ -967,10 +986,7 @@ module initia_std::multisig_v2 { assert!(!is_exist(addr1, name), 1); create_non_weighted_multisig_account( - &account1, - name, - vector[addr1, addr2, addr3], - 2 + &account1, name, vector[addr1, addr2, addr3], 2 ); assert!(is_exist(addr1, name), 1) @@ -1274,14 +1290,17 @@ module initia_std::multisig_v2 { ); // assert each member tier is correct + let index = 0; vector::for_each_ref( &multisig_wallet.members, |member| { - let (_, index) = vector::index_of(&multisig_wallet.members, member); + let tier_name = *vector::borrow(&member_tiers, index); + index = index + 1; + let m: &Member = member; let tier = option::borrow(&m.tier); assert!( - *vector::borrow(&member_tiers, index) == tier.name, + tier_name == tier.name, 1 ) } @@ -1370,12 +1389,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::none() ); } @@ -1413,12 +1434,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2"), string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::none() ); } @@ -1457,12 +1480,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -1489,7 +1514,7 @@ module initia_std::multisig_v2 { ); assert!(proposal.status == 0, 1); assert!(proposal.is_json == false, 1); - + let execute_message = vector::borrow(&proposal.execute_messages, 0); assert!(vector::length(&execute_message.json_args) == 0, 1); assert!( @@ -1566,12 +1591,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -1617,7 +1644,7 @@ module initia_std::multisig_v2 { ); } - #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] fun proposal_with_json( account1: signer, account2: signer, account3: signer ) acquires MultisigWallet { @@ -1643,12 +1670,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - string::utf8(b"[\"0x101\", \"0x102\", \"0x104\"]"), - string::utf8(b"\"3\""), - string::utf8(b""), - string::utf8(b"") - ]], + vector[ + vector[ + string::utf8(b"[\"0x101\", \"0x102\", \"0x104\"]"), + string::utf8(b"\"3\""), + string::utf8(b""), + string::utf8(b"") + ] + ], option::some(99) ); @@ -1713,12 +1742,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -1759,12 +1790,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -1805,12 +1838,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -1876,12 +1911,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -1940,12 +1977,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -1989,12 +2028,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -2034,12 +2075,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -2091,12 +2134,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -2147,12 +2192,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr4]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -2259,13 +2306,15 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr3]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr3]), std::bcs::to_bytes( + &3u64 + ), std::bcs::to_bytes(&option::none()), std::bcs::to_bytes( + &option::none() + ), std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); @@ -2282,12 +2331,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr3]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr3]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(1) ); @@ -2307,12 +2358,14 @@ module initia_std::multisig_v2 { vector[string::utf8(b"multisig_v2")], vector[string::utf8(b"update_config")], vector[vector[]], - vector[vector[ - std::bcs::to_bytes(&vector[addr1, addr2, addr3]), - std::bcs::to_bytes(&3u64), - std::bcs::to_bytes(&option::none()), - std::bcs::to_bytes(&option::none()) - ]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr3]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], option::some(99) ); diff --git a/precompile/modules/minitia_stdlib/sources/multisig_v2.move b/precompile/modules/minitia_stdlib/sources/multisig_v2.move new file mode 100644 index 00000000..6a1e2355 --- /dev/null +++ b/precompile/modules/minitia_stdlib/sources/multisig_v2.move @@ -0,0 +1,2472 @@ +module minitia_std::multisig_v2 { + use std::error; + use std::option::{Self, Option}; + use std::signer; + use std::string::{Self, String}; + use std::vector; + use std::event; + + use minitia_std::block::get_block_info; + use minitia_std::cosmos::{move_execute, move_execute_with_json}; + use minitia_std::object::{Self, ExtendRef}; + use minitia_std::simple_map::{Self, SimpleMap}; + use minitia_std::table::{Self, Table}; + use minitia_std::type_info; + + // errors + + const EINVALID_THRESHOLD: u64 = 1; + + const ENOT_MEMBER: u64 = 2; + + const EINVALID_PROPOSAL_STATUS: u64 = 3; + + const EPROPOSAL_EXPIRED: u64 = 4; + + const ENOT_PASS: u64 = 5; + + const EPROPOSAL_NOT_FOUND: u64 = 6; + + const EINVALID_TIERS_LENGTH: u64 = 7; + + const EINVALID_MEMBERS_LENGTH: u64 = 8; + + const EINVALID_MEMBER_TIERS: u64 = 9; + + const EINVALID_EXPIRY_TIMESTAMP: u64 = 10; + + const EMULTISIG_NAME_TOO_LONG: u64 = 11; + + const EINVALID_PROPOSAL_MESSAGE_LENGTH: u64 = 12; + + // constants + + const STATUS: vector> = vector[b"voting", b"executed", b"expired"]; + + const MAX_LIMIT: u8 = 30; + + const MAX_MULTISIG_NAME_LENGTH: u64 = 64; + + // structs + + struct Tier has copy, drop, store { + name: String, + weight: u64 + } + + struct Member has copy, drop, store { + address: address, + tier: Option + } + + struct MultisigWallet has key { + extend_ref: ExtendRef, + name: String, + weighted: bool, // if true -> tiers should be present + tiers: Option>, + members: vector, // members of multisig account + threshold: u64, // require weight to pass + proposals: Table + } + + struct ExecuteMessage has copy, drop, store { + module_address: address, + module_name: String, + function_name: String, + type_args: vector, + args: vector>, + json_args: vector + } + + struct Proposal has store { + proposer: Member, + proposed_timestamp: u64, + proposed_height: u64, + expiry_timestamp: Option, + votes: SimpleMap, + threshold: u64, + total_weight: u64, + status: u8, + is_json: bool, + execute_messages: vector + } + + // events + + #[event] + struct CreateMultisigAccountEvent has drop, store { + multisig_addr: address, + name: String, + weighted: bool, + members: vector, + threshold: u64 + } + + #[event] + struct CreateProposalEvent has drop, store { + multisig_addr: address, + proposal_id: u64, + proposer: Member, + execute_messages: vector + } + + #[event] + struct VoteProposalEvent has drop, store { + multisig_addr: address, + proposal_id: u64, + voter: Member, + vote_yes: bool + } + + #[event] + struct ExecuteProposalEvent has drop, store { + multisig_addr: address, + proposal_id: u64, + executor: Member + } + + #[event] + struct UpdateConfigEvent has drop, store { + multisig_addr: address, + members: vector, + tiers: Option>, + threshold: u64 + } + + // view function response struct + + struct ProposalResponse has drop { + multisig_addr: address, + proposal_id: u64, + votes: SimpleMap, + proposer: Member, + proposed_height: u64, + proposed_timestamp: u64, + expiry_timestamp: Option, + threshold: u64, + total_weight: u64, + yes_vote_score: u64, + status: String, + is_json: bool, + execute_messages: vector + } + + struct MultisigResponse has drop { + multisig_addr: address, + name: String, + members: vector, + threshold: u64, + tiers: Option> + } + + // view functions + + #[view] + public fun is_exist(creator_addr: address, name: String): bool { + let seed = create_multisig_seed(&name); + let multisig_addr = object::create_object_address(&creator_addr, seed); + object::object_exists(multisig_addr) + } + + #[view] + public fun get_multisig(multisig_addr: address): MultisigResponse acquires MultisigWallet { + let multisig_wallet = borrow_global(multisig_addr); + + MultisigResponse { + multisig_addr, + name: multisig_wallet.name, + tiers: multisig_wallet.tiers, + members: multisig_wallet.members, + threshold: multisig_wallet.threshold + } + } + + #[view] + public fun get_proposal( + multisig_addr: address, proposal_id: u64 + ): ProposalResponse acquires MultisigWallet { + let multisig_wallet = borrow_global(multisig_addr); + let proposal = table::borrow(&multisig_wallet.proposals, proposal_id); + proposal_to_proposal_response( + multisig_wallet, + multisig_addr, + proposal_id, + proposal + ) + } + + #[view] + public fun get_proposals( + multisig_addr: address, start_after: Option, limit: u8 + ): vector acquires MultisigWallet { + if (limit > MAX_LIMIT) { + limit = MAX_LIMIT + }; + let res: vector = vector[]; + let multisig_wallet = borrow_global(multisig_addr); + let iter = table::iter( + &multisig_wallet.proposals, + option::none(), + start_after, + 2 + ); + + while (vector::length(&res) < (limit as u64) + && table::prepare(iter)) { + let (proposal_id, proposal) = table::next(iter); + vector::push_back( + &mut res, + proposal_to_proposal_response( + multisig_wallet, + multisig_addr, + proposal_id, + proposal + ) + ); + }; + + res + } + + // entry functions + + /// Create new non weighted multisig account + public entry fun create_non_weighted_multisig_account( + account: &signer, + name: String, // name for make deterministic multisig address (account_addr + name) + members: vector
, + threshold: u64 + ) { + assert_member(&members, &signer::address_of(account)); + assert!( + vector::length(&members) >= threshold, + error::invalid_argument(EINVALID_THRESHOLD) + ); + assert!( + threshold > 0, + error::invalid_argument(EINVALID_THRESHOLD) + ); + + let constructor_ref = + object::create_named_object(account, create_multisig_seed(&name)); + let extend_ref = object::generate_extend_ref(&constructor_ref); + let multisig_signer = object::generate_signer(&constructor_ref); + let multisig_addr = signer::address_of(&multisig_signer); + + assert_uniqueness(members); + + let members = vector::map( + members, + |member| Member { address: member, tier: option::none() } + ); + + move_to( + &multisig_signer, + MultisigWallet { + extend_ref, + name, + members, + weighted: false, + tiers: option::none(), + threshold, + proposals: table::new() + } + ); + + event::emit( + CreateMultisigAccountEvent { + multisig_addr, + name, + weighted: false, + members, + threshold + } + ) + } + + /// Create new weighted multisig account + public entry fun create_weighted_multisig_account( + account: &signer, + name: String, // name for make deterministic multisig address (account_addr + name) + tiers: vector, + tier_weights: vector, + members: vector
, + member_tiers: vector, + threshold: u64 + ) { + assert_member(&members, &signer::address_of(account)); + assert_uniqueness(members); + assert_tier_config(tiers, tier_weights, &members, member_tiers); + assert_uniqueness(tiers); + + // check threshold computed from each member weights + let total_weight = vector::fold( + member_tiers, + 0u64, + |acc, tier| { + let (_, index) = vector::index_of(&tiers, &tier); + acc + *vector::borrow(&tier_weights, index) + } + ); + assert!( + total_weight >= threshold, + error::invalid_argument(EINVALID_THRESHOLD) + ); + assert!( + threshold > 0, + error::invalid_argument(EINVALID_THRESHOLD) + ); + + let constructor_ref = + object::create_named_object(account, create_multisig_seed(&name)); + let extend_ref = object::generate_extend_ref(&constructor_ref); + let multisig_signer = object::generate_signer(&constructor_ref); + let multisig_addr = signer::address_of(&multisig_signer); + + let tiers = vector::map( + tiers, + |tier| { + let (_, index) = vector::index_of(&tiers, &tier); + Tier { name: tier, weight: *vector::borrow(&tier_weights, index) } + } + ); + + let members = construct_members_with_tiers(members, member_tiers, tiers); + + move_to( + &multisig_signer, + MultisigWallet { + extend_ref, + name, + members, + weighted: true, + tiers: option::some(tiers), + threshold, + proposals: table::new() + } + ); + + event::emit( + CreateMultisigAccountEvent { + multisig_addr, + weighted: true, + name, + members, + threshold + } + ) + } + + fun total_weight(members: &vector): u64 { + vector::fold( + *members, + 0u64, + |acc, member| { + let m: Member = member; + acc + + if (option::is_some(&m.tier)) { + let tier = *option::borrow(&m.tier); + tier.weight + } else { 1u64 } + } + ) + } + + /// Create new proposal + public entry fun create_proposal( + account: &signer, + multisig_addr: address, + module_address_list: vector
, + module_name_list: vector, + function_name_list: vector, + type_args_list: vector>, + args_list: vector>>, + expiry_duration: Option + ) acquires MultisigWallet { + assert!( + vector::length(&module_address_list) == vector::length(&module_name_list) + && vector::length(&module_name_list) + == vector::length(&function_name_list) + && vector::length(&function_name_list) + == vector::length(&type_args_list) + && vector::length(&type_args_list) == vector::length(&args_list), + error::invalid_argument(EINVALID_PROPOSAL_MESSAGE_LENGTH) + ); + + let index = 0; + let execute_messages = vector::map( + module_address_list, + |module_address| { + let module_name = *vector::borrow(&module_name_list, index); + let function_name = *vector::borrow(&function_name_list, index); + let type_args = *vector::borrow(&type_args_list, index); + let args = *vector::borrow(&args_list, index); + index = index + 1; + + ExecuteMessage { + module_address, + module_name, + function_name, + type_args, + args, + json_args: vector[] + } + } + ); + + create_proposal_internal( + account, + multisig_addr, + false, + execute_messages, + expiry_duration + ) + } + + /// Create new proposal + public entry fun create_proposal_with_json( + account: &signer, + multisig_addr: address, + module_address_list: vector
, + module_name_list: vector, + function_name_list: vector, + type_args_list: vector>, + args_list: vector>, + expiry_duration: Option + ) acquires MultisigWallet { + assert!( + vector::length(&module_address_list) == vector::length(&module_name_list) + && vector::length(&module_name_list) + == vector::length(&function_name_list) + && vector::length(&function_name_list) + == vector::length(&type_args_list) + && vector::length(&type_args_list) == vector::length(&args_list), + error::invalid_argument(EINVALID_PROPOSAL_MESSAGE_LENGTH) + ); + + let index = 0; + let execute_messages = vector::map( + module_address_list, + |module_address| { + let module_name = *vector::borrow(&module_name_list, index); + let function_name = *vector::borrow(&function_name_list, index); + let type_args = *vector::borrow(&type_args_list, index); + let json_args = *vector::borrow(&args_list, index); + index = index + 1; + + ExecuteMessage { + module_address, + module_name, + function_name, + type_args, + args: vector[], + json_args + } + } + ); + + create_proposal_internal( + account, + multisig_addr, + true, + execute_messages, + expiry_duration + ) + } + + /// Vote proposal + public entry fun vote_proposal( + account: &signer, + multisig_addr: address, + proposal_id: u64, + vote_yes: bool + ) acquires MultisigWallet { + let voter_address = signer::address_of(account); + let multisig_wallet = borrow_global_mut(multisig_addr); + assert_multisig_member(&multisig_wallet.members, &voter_address); + + assert!( + table::contains(&multisig_wallet.proposals, proposal_id), + error::invalid_argument(EPROPOSAL_NOT_FOUND) + ); + let proposal = table::borrow_mut(&mut multisig_wallet.proposals, proposal_id); + + assert_proposal(proposal); + + let voter = get_member_by_address(multisig_wallet.members, voter_address); + vote(&mut proposal.votes, voter, vote_yes); + + event::emit( + VoteProposalEvent { multisig_addr, proposal_id, voter, vote_yes } + ) + } + + /// Execute proposal + public entry fun execute_proposal( + account: &signer, multisig_addr: address, proposal_id: u64 + ) acquires MultisigWallet { + let executor_address = signer::address_of(account); + let multisig_wallet = borrow_global_mut(multisig_addr); + let executor = get_member_by_address(multisig_wallet.members, executor_address); + assert_multisig_member(&multisig_wallet.members, &executor_address); + + assert!( + table::contains(&multisig_wallet.proposals, proposal_id), + error::invalid_argument(EPROPOSAL_NOT_FOUND) + ); + let proposal = table::borrow_mut(&mut multisig_wallet.proposals, proposal_id); + + assert_proposal(proposal); + + // check passed + assert!( + yes_vote_score(&proposal.votes, &multisig_wallet.members) + >= multisig_wallet.threshold, + error::invalid_state(ENOT_PASS) + ); + + let multisig_signer = + &object::generate_signer_for_extending(&multisig_wallet.extend_ref); + + proposal.status = 1; // change the status first in case of updating config + + if (!proposal.is_json) { + vector::for_each( + proposal.execute_messages, + |execute_message| { + let m: ExecuteMessage = execute_message; + move_execute( + multisig_signer, + m.module_address, + m.module_name, + m.function_name, + m.type_args, + m.args + ) + } + ) + } else { + vector::for_each( + proposal.execute_messages, + |execute_message| { + let m: ExecuteMessage = execute_message; + move_execute_with_json( + multisig_signer, + m.module_address, + m.module_name, + m.function_name, + m.type_args, + m.json_args + ) + } + ) + }; + + event::emit( + ExecuteProposalEvent { multisig_addr, proposal_id, executor } + ) + } + + /// Update config. Only execute by multisig wallet itself + public entry fun update_config( + account: &signer, + new_members: vector
, + new_tiers: Option>, + new_tier_weights: Option>, + new_member_tiers: Option>, + new_threshold: u64 + ) acquires MultisigWallet { + let multisig_addr = signer::address_of(account); + let multisig_wallet = borrow_global_mut(multisig_addr); + + assert_uniqueness(new_members); + + let removed_members = vector::filter( + multisig_wallet.members, + |member| { + let m: &Member = member; + !vector::contains(&new_members, &m.address) + } + ); + + if (multisig_wallet.weighted) { + let new_tiers = *option::borrow(&new_tiers); + let new_tier_weights = *option::borrow(&new_tier_weights); + let new_member_tiers = *option::borrow(&new_member_tiers); + + assert_tier_config( + new_tiers, + new_tier_weights, + &new_members, + new_member_tiers + ); + assert_uniqueness(new_tiers); + + // check threshold computed from each member weights + let total_weight = vector::fold( + new_member_tiers, + 0u64, + |acc, tier| { + let (_, index) = vector::index_of(&new_tiers, &tier); + acc + *vector::borrow(&new_tier_weights, index) + } + ); + + assert!( + total_weight >= new_threshold, + error::invalid_argument(EINVALID_THRESHOLD) + ); + assert!( + new_threshold > 0, + error::invalid_argument(EINVALID_THRESHOLD) + ); + + let tiers = vector::map( + new_tiers, + |tier| { + let (_, index) = vector::index_of(&new_tiers, &tier); + Tier { name: tier, weight: *vector::borrow(&new_tier_weights, index) } + } + ); + + multisig_wallet.threshold = new_threshold; + multisig_wallet.tiers = option::some(tiers); + multisig_wallet.members = construct_members_with_tiers( + new_members, new_member_tiers, tiers + ); + } else { + assert!( + vector::length(&new_members) >= new_threshold, + error::invalid_argument(EINVALID_THRESHOLD) + ); + + multisig_wallet.threshold = new_threshold; + multisig_wallet.members = vector::map( + new_members, + |member| Member { address: member, tier: option::none() } + ); + }; + + // remove votes of the removed members from active proposals + let iter = + table::iter_mut( + &mut multisig_wallet.proposals, + option::none(), + option::none(), + 2 + ); + while (table::prepare(iter)) { + let (_, proposal) = table::next_mut(iter); + // only cares about active proposals + if (proposal.status != 0 || is_proposal_expired(proposal)) { + continue + }; + + proposal.threshold = new_threshold; + proposal.total_weight = total_weight(&multisig_wallet.members); + + // remove removed_members votes + vector::for_each( + removed_members, + |member| { + let m: Member = member; + if (simple_map::contains_key(&proposal.votes, &m)) { + simple_map::remove(&mut proposal.votes, &m); + } + } + ); + }; + + event::emit( + UpdateConfigEvent { + multisig_addr, + members: multisig_wallet.members, + tiers: multisig_wallet.tiers, + threshold: multisig_wallet.threshold + } + ) + } + + // public functions + + public fun create_multisig_seed(name: &String): vector { + assert!( + string::length(name) <= MAX_MULTISIG_NAME_LENGTH, + error::out_of_range(EMULTISIG_NAME_TOO_LONG) + ); + + let type_name = type_info::type_name(); + let seed = *string::bytes(&type_name); + vector::append(&mut seed, *string::bytes(name)); + seed + } + + // private functions + + fun construct_members_with_tiers( + members: vector
, member_tiers: vector, tiers: vector + ): vector { + let index = 0; + vector::map( + members, + |member| { + let tier_name = *vector::borrow(&member_tiers, index); + index = index + 1; + + // find tier with tier_name in tiers + let (found, tier_index) = vector::find( + &tiers, + |t| { + let tt: &Tier = t; + tt.name == tier_name + } + ); + + assert!(found, error::invalid_argument(EINVALID_MEMBER_TIERS)); + + let tier = *vector::borrow(&tiers, tier_index); + + Member { address: member, tier: option::some(tier) } + } + ) + } + + fun get_member_by_address(members: vector, address: address): Member { + let (found, index) = vector::find( + &members, + |member| { + let m: &Member = member; + m.address == address + } + ); + + assert!(found, error::permission_denied(ENOT_MEMBER)); + + *vector::borrow(&members, index) + } + + fun create_proposal_internal( + account: &signer, + multisig_addr: address, + is_json: bool, + execute_messages: vector, + expiry_duration: Option + ) acquires MultisigWallet { + let addr = signer::address_of(account); + let multisig_wallet = borrow_global_mut(multisig_addr); + assert_multisig_member(&multisig_wallet.members, &addr); + + let (height, timestamp) = get_block_info(); + + let expiry_timestamp = + if (option::is_some(&expiry_duration)) { + let time_until_expired = *option::borrow(&expiry_duration); + option::some(timestamp + time_until_expired) + } else { + option::none() + }; + + // proposer votes yes on proposal creation + let votes = simple_map::create(); + let proposer = get_member_by_address(multisig_wallet.members, addr); + simple_map::add(&mut votes, proposer, true); + + let proposal = Proposal { + proposer, + proposed_height: height, + proposed_timestamp: timestamp, + expiry_timestamp, + threshold: multisig_wallet.threshold, + total_weight: total_weight(&multisig_wallet.members), + votes, + status: 0, // in voting period + is_json, + execute_messages + }; + + let proposal_id = table::length(&multisig_wallet.proposals) + 1; + table::add( + &mut multisig_wallet.proposals, + proposal_id, + proposal + ); + + event::emit( + CreateProposalEvent { multisig_addr, proposal_id, proposer, execute_messages } + ) + } + + fun is_proposal_expired(proposal: &Proposal): bool { + let (_, timestamp) = get_block_info(); + + if (option::is_none(&proposal.expiry_timestamp)) { + return false + }; + + let expiry = *option::borrow(&proposal.expiry_timestamp); + + return timestamp >= expiry + } + + fun vote( + votes: &mut SimpleMap, + voter: Member, + vote_yes: bool + ) { + if (simple_map::contains_key(votes, &voter)) { + let vote = simple_map::borrow_mut(votes, &voter); + *vote = vote_yes; + } else { + simple_map::add(votes, voter, vote_yes); + }; + } + + fun yes_vote_score( + votes: &SimpleMap, members: &vector + ): u64 { + vector::fold( + *members, + 0u64, + |acc, member| { + let m: Member = member; + let weight = + if (option::is_some(&m.tier)) { + let tier = *option::borrow(&m.tier); + tier.weight + } else { 1u64 }; + if (simple_map::contains_key(votes, &m) + && *simple_map::borrow(votes, &m)) { + acc + weight + } else { acc } + } + ) + } + + fun proposal_to_proposal_response( + multisig_wallet: &MultisigWallet, + multisig_addr: address, + proposal_id: u64, + proposal: &Proposal + ): ProposalResponse { + let status_index = proposal.status; + let is_expired = is_proposal_expired(proposal); + let yes_vote_score = yes_vote_score(&proposal.votes, &multisig_wallet.members); + if (status_index == 0 && is_expired) { + status_index = 2 + }; + + ProposalResponse { + multisig_addr, + proposal_id, + proposer: proposal.proposer, + proposed_height: proposal.proposed_height, + proposed_timestamp: proposal.proposed_timestamp, + expiry_timestamp: proposal.expiry_timestamp, + votes: proposal.votes, + threshold: proposal.threshold, + total_weight: proposal.total_weight, + yes_vote_score, + status: string::utf8(*vector::borrow(&STATUS, (status_index as u64))), + is_json: proposal.is_json, + execute_messages: proposal.execute_messages + } + } + + inline fun assert_uniqueness(vec: vector) { + let m = simple_map::create(); + vector::for_each(vec, |elem| simple_map::add(&mut m, elem, true)) + } + + inline fun assert_member(members: &vector
, member: &address) { + assert!( + vector::contains(members, member), + error::permission_denied(ENOT_MEMBER) + ) + } + + inline fun assert_multisig_member( + multisig_members: &vector, member: &address + ) { + let member_addresses = vector::map_ref( + multisig_members, + |multisig_member| { + let m: &Member = multisig_member; + m.address + } + ); + + assert_member(&member_addresses, member) + } + + inline fun assert_proposal(proposal: &Proposal) { + assert!( + proposal.status == 0, + error::invalid_state(EINVALID_PROPOSAL_STATUS) + ); + assert!( + !is_proposal_expired(proposal), + error::invalid_state(EPROPOSAL_EXPIRED) + ); + } + + inline fun assert_tier_config( + tiers: vector, + tier_weights: vector, + members: &vector
, + member_tiers: vector + ) { + assert!( + vector::length(&tiers) == vector::length(&tier_weights), + error::invalid_argument(EINVALID_TIERS_LENGTH) + ); + + assert!( + vector::length(members) == vector::length(&member_tiers), + error::invalid_argument(EINVALID_MEMBERS_LENGTH) + ); + + vector::for_each( + member_tiers, + |tier| assert!( + vector::contains(&tiers, &tier), + error::invalid_argument(EINVALID_MEMBER_TIERS) + ) + ); + } + + #[test_only] + use minitia_std::address; + #[test_only] + use minitia_std::block::set_block_info; + + #[test_only] + fun get_multisig_address(creator: &address, name: &String): address { + let seed = address::to_string(@minitia_std); + string::append(&mut seed, string::utf8(b"::multisig_v2::MultisigWallet")); + string::append(&mut seed, *name); + + object::create_object_address(creator, *string::bytes(&seed)) + } + + // create test_only function for create votes map + #[test_only] + fun create_votes_map(members: vector, votes: vector): + SimpleMap { + let votes_map = simple_map::create(); + let index = 0; + vector::for_each( + members, + |member| { + let vote = *vector::borrow(&votes, index); + index = index + 1; + + simple_map::add( + &mut votes_map, + member, + vote + ) + } + ); + + votes_map + } + + // view functions tests + + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + fun is_exist_test( + account1: signer, account2: signer, account3: signer + ) { + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + let name = string::utf8(b"multisig wallet"); + + assert!(!is_exist(addr1, name), 1); + + create_non_weighted_multisig_account( + &account1, name, vector[addr1, addr2, addr3], 2 + ); + + assert!(is_exist(addr1, name), 1) + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x50002, location = Self)] + fun create_non_weighted_wallet_by_other( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_non_weighted_multisig_account( + &account4, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + } + + // test multisig wallet name too long + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + #[expected_failure(abort_code = 0x2000b, location = Self)] + fun wallet_name_too_long( + account1: signer, account2: signer, account3: signer + ) { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_non_weighted_multisig_account( + &account1, + string::utf8( + b"multimultimultimultimultimultimultimultimultimultimultimultimulti" + ), // 64 letters + vector[addr1, addr2, addr3], + 2 + ); + } + + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun non_weighted_invalid_threshold( + account1: signer, account2: signer, account3: signer + ) { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 4 + ); + } + + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + #[expected_failure(abort_code = 0x10001, location = simple_map)] + fun non_weighted_duplicate_members( + account1: signer, account2: signer, account3: signer + ) { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr1, addr2, addr3], + 3 + ); + } + + // test create weight multisig wallet successfully, check keys in object + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + fun create_non_weighted_wallet_success( + account1: signer, account2: signer, account3: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 3 + ); + + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + let multisig_wallet = borrow_global(multisig_addr); + assert!( + vector::length(&multisig_wallet.members) == 3, + error::invalid_state(EINVALID_MEMBERS_LENGTH) + ); + + // assert each member tier is correct + vector::for_each_ref( + &multisig_wallet.members, + |member| { + let m: &Member = member; + assert!(option::is_none(&m.tier), 1) + } + ); + + assert!( + multisig_wallet.threshold == 3, + error::invalid_state(EINVALID_THRESHOLD) + ); + + assert!( + !multisig_wallet.weighted, + error::invalid_state(1) + ); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x50002, location = Self)] + fun create_weighted_wallet_by_other( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_weighted_multisig_account( + &account4, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[2, 1], + vector[addr1, addr2, addr3], + vector[ + string::utf8(b"admin"), + string::utf8(b"member"), + string::utf8(b"member") + ], + 2 + ); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x10007, location = Self)] + fun weighted_invalid_tiers_length( + account1: signer, account2: signer, account3: signer + ) { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[2], + vector[addr1, addr2, addr3], + vector[ + string::utf8(b"admin"), + string::utf8(b"member"), + string::utf8(b"member") + ], + 2 + ); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x10008, location = Self)] + fun weighted_invalid_members_length( + account1: signer, account2: signer, account3: signer + ) { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[2, 1], + vector[addr1, addr2, addr3], + vector[string::utf8(b"admin"), string::utf8(b"member")], + 2 + ); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x10009, location = Self)] + fun weighted_invalid_members_tiers( + account1: signer, account2: signer, account3: signer + ) { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[2, 1], + vector[addr1, addr2, addr3], + vector[ + string::utf8(b"admin"), + string::utf8(b"member"), + string::utf8(b"god") + ], + 2 + ); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun weighted_invalid_threshold( + account1: signer, account2: signer, account3: signer + ) { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[10, 1], + vector[addr1, addr2, addr3], + vector[ + string::utf8(b"admin"), + string::utf8(b"admin"), + string::utf8(b"member") + ], + 22 + ); + } + + // test create weight multisig wallet successfully, check keys in object + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + fun create_weighted_wallet_success( + account1: signer, account2: signer, account3: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + let member_tiers = vector[ + string::utf8(b"admin"), + string::utf8(b"member"), + string::utf8(b"member") + ]; + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[2, 1], + vector[addr1, addr2, addr3], + member_tiers, + 2 + ); + + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + let multisig_wallet = borrow_global(multisig_addr); + assert!( + vector::length(&multisig_wallet.members) == 3, + error::invalid_state(EINVALID_MEMBERS_LENGTH) + ); + + // assert each member tier is correct + let index = 0; + vector::for_each_ref( + &multisig_wallet.members, + |member| { + let tier_name = *vector::borrow(&member_tiers, index); + index = index + 1; + + let m: &Member = member; + let tier = option::borrow(&m.tier); + assert!( + tier_name == tier.name, + 1 + ) + } + ); + + // assert if multisig_wallet.tiers is not none and length is 2 + let tiers = option::borrow(&multisig_wallet.tiers); + assert!( + vector::length(tiers) == 2, + error::invalid_state(EINVALID_TIERS_LENGTH) + ); + + assert!( + multisig_wallet.threshold == 2, + error::invalid_state(EINVALID_THRESHOLD) + ); + + assert!( + multisig_wallet.weighted, + error::invalid_state(1) + ); + } + + // test total_weight(members) + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + fun total_weight_test( + account1: signer, account2: signer, account3: signer + ) acquires MultisigWallet { + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[2, 1], + vector[addr1, addr2, addr3], + vector[ + string::utf8(b"admin"), + string::utf8(b"member"), + string::utf8(b"member") + ], + 2 + ); + + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + let multisig_wallet = borrow_global(multisig_addr); + + let total_weight = total_weight(&multisig_wallet.members); + assert!(total_weight == 4, 1); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x50002, location = Self)] + fun create_proposal_by_other( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + create_proposal( + &account4, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::none() + ); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x1000c, location = Self)] + fun invalid_list_create_proposal( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + create_proposal( + &account4, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2"), string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::none() + ); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + fun create_proposal_successfully( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + set_block_info(100, 100); + + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + // borrow proposal from multisig wallet + let multisig_wallet = borrow_global(multisig_addr); + let proposal = table::borrow(&multisig_wallet.proposals, 1); + + let expected_votes = simple_map::create(); + simple_map::add( + &mut expected_votes, + get_member_by_address(multisig_wallet.members, addr1), + true + ); + + assert!( + proposal.proposer == get_member_by_address(multisig_wallet.members, addr1), + 1 + ); + assert!(proposal.proposed_height == 100, 1); + assert!(proposal.proposed_timestamp == 100, 1); + assert!( + *option::borrow(&proposal.expiry_timestamp) == 199, + 1 + ); + assert!(proposal.status == 0, 1); + assert!(proposal.is_json == false, 1); + + let execute_message = vector::borrow(&proposal.execute_messages, 0); + assert!(vector::length(&execute_message.json_args) == 0, 1); + assert!( + &execute_message.args + == &vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ], + 1 + ); + + assert!(vector::length(&execute_message.type_args) == 0, 1); + assert!(execute_message.module_address == @minitia_std, 1); + assert!( + execute_message.module_name == string::utf8(b"multisig_v2"), + 1 + ); + assert!( + execute_message.function_name == string::utf8(b"update_config"), + 1 + ); + assert!( + proposal.threshold == multisig_wallet.threshold, + 1 + ); + assert!(proposal.total_weight == 3, 1); + assert!( + yes_vote_score(&proposal.votes, &multisig_wallet.members) == 1, + 1 + ); + } + + // test proposal_to_proposal_response for weight multisig wallet + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + fun proposal_to_proposal_response_weighted( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[2, 1], + vector[addr1, addr2, addr3], + vector[ + string::utf8(b"admin"), + string::utf8(b"member"), + string::utf8(b"member") + ], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + set_block_info(100, 100); + + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + let multisig_wallet = borrow_global(multisig_addr); + let proposal = table::borrow(&multisig_wallet.proposals, 1); + + let proposal_response = + proposal_to_proposal_response(multisig_wallet, multisig_addr, 1, proposal); + + let expected_proposal_response = ProposalResponse { + multisig_addr, + proposal_id: 1, + proposer: get_member_by_address(multisig_wallet.members, addr1), + proposed_height: 100, + proposed_timestamp: 100, + expiry_timestamp: option::some(199), + votes: proposal.votes, + threshold: 2, + total_weight: 4, + yes_vote_score: 2, + status: string::utf8(b"voting"), + is_json: false, + execute_messages: vector[ + ExecuteMessage { + module_address: @minitia_std, + module_name: string::utf8(b"multisig_v2"), + function_name: string::utf8(b"update_config"), + type_args: vector[], + args: vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ], + json_args: vector[] + } + ] + }; + + assert!( + proposal_response == expected_proposal_response, + 1 + ); + } + + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + fun proposal_with_json( + account1: signer, account2: signer, account3: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + create_proposal_with_json( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + string::utf8(b"[\"0x101\", \"0x102\", \"0x104\"]"), + string::utf8(b"\"3\""), + string::utf8(b""), + string::utf8(b"") + ] + ], + option::some(99) + ); + + let proposal = get_proposal(multisig_addr, 1); + let execute_message = vector::borrow(&proposal.execute_messages, 0); + + assert!(execute_message.module_address == @minitia_std, 0); + assert!( + execute_message.module_name == string::utf8(b"multisig_v2"), + 1 + ); + assert!( + execute_message.function_name == string::utf8(b"update_config"), + 2 + ); + assert!(execute_message.type_args == vector[], 3); + assert!( + execute_message.json_args + == vector[ + string::utf8(b"[\"0x101\", \"0x102\", \"0x104\"]"), + string::utf8(b"\"3\""), + string::utf8(b""), + string::utf8(b"") + ], + 4 + ); + assert!(execute_message.args == vector[], 5); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x50002, location = Self)] + fun vote_by_other( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + set_block_info(100, 100); + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + vote_proposal(&account4, multisig_addr, 1, true); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x30004, location = Self)] + fun vote_after_proposal_expired( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + set_block_info(100, 100); + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + set_block_info(100, 199); + vote_proposal(&account2, multisig_addr, 1, true); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + fun vote_proposal_of_non_weighted_multisig_successfully( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + set_block_info(100, 100); + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + vote_proposal(&account1, multisig_addr, 1, true); + vote_proposal(&account2, multisig_addr, 1, false); + vote_proposal(&account3, multisig_addr, 1, true); + + let multisig_wallet = borrow_global(multisig_addr); + let proposal = table::borrow(&multisig_wallet.proposals, 1); + let proposal_response = + proposal_to_proposal_response(multisig_wallet, multisig_addr, 1, proposal); + + let expected_votes = + create_votes_map( + multisig_wallet.members, + vector[true, false, true] + ); + + assert!(proposal_response.votes == expected_votes, 1); + + assert!(proposal_response.yes_vote_score == 2, 1); + + assert!(proposal_response.total_weight == 3, 1); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + fun vote_proposal_of_weighted_multisig_successfully( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[2, 1], + vector[addr1, addr2, addr3], + vector[ + string::utf8(b"admin"), + string::utf8(b"member"), + string::utf8(b"member") + ], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + set_block_info(100, 100); + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + vote_proposal(&account1, multisig_addr, 1, true); + vote_proposal(&account2, multisig_addr, 1, false); + vote_proposal(&account3, multisig_addr, 1, true); + + let multisig_wallet = borrow_global(multisig_addr); + let proposal = table::borrow(&multisig_wallet.proposals, 1); + let proposal_response = + proposal_to_proposal_response(multisig_wallet, multisig_addr, 1, proposal); + + let expected_votes = + create_votes_map( + multisig_wallet.members, + vector[true, false, true] + ); + + assert!(proposal_response.votes == expected_votes, 1); + + assert!(proposal_response.yes_vote_score == 3, 1); + + assert!(proposal_response.total_weight == 4, 1); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x50002, location = Self)] + fun execute_by_others( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + vote_proposal(&account1, multisig_addr, 1, true); + vote_proposal(&account2, multisig_addr, 1, false); + vote_proposal(&account3, multisig_addr, 1, false); + + execute_proposal(&account4, multisig_addr, 1); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x10006, location = Self)] + fun execute_on_a_non_existing_proposal( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + execute_proposal(&account1, multisig_addr, 2); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x30005, location = Self)] + fun non_weighted_multisig_execute_not_pass( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + vote_proposal(&account1, multisig_addr, 1, true); + vote_proposal(&account2, multisig_addr, 1, false); + vote_proposal(&account3, multisig_addr, 1, false); + + execute_proposal(&account1, multisig_addr, 1); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + #[expected_failure(abort_code = 0x30005, location = Self)] + fun weighted_multisig_execute_not_pass( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[3, 2], + vector[addr1, addr2, addr3], + vector[ + string::utf8(b"admin"), + string::utf8(b"member"), + string::utf8(b"member") + ], + 6 + ); + + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + vote_proposal(&account1, multisig_addr, 1, true); + vote_proposal(&account2, multisig_addr, 1, false); + vote_proposal(&account3, multisig_addr, 1, true); + + execute_proposal(&account1, multisig_addr, 1); + } + + #[test( + account1 = @0x101, account2 = @0x102, account3 = @0x103, account4 = @0x104 + )] + fun execute_pass_successfully( + account1: signer, + account2: signer, + account3: signer, + account4: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + let addr4 = signer::address_of(&account4); + + create_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[string::utf8(b"admin"), string::utf8(b"member")], + vector[3, 2], + vector[addr1, addr2, addr3], + vector[ + string::utf8(b"admin"), + string::utf8(b"member"), + string::utf8(b"member") + ], + 5 + ); + + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr4]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + vote_proposal(&account1, multisig_addr, 1, true); + vote_proposal(&account2, multisig_addr, 1, false); + vote_proposal(&account3, multisig_addr, 1, true); + + execute_proposal(&account1, multisig_addr, 1); + } + + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + #[expected_failure(abort_code = 0x10001, location = simple_map)] + fun update_config_duplicate_members( + account1: signer, account2: signer, account3: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + let multisig_wallet = borrow_global(multisig_addr); + let multisig_signer = + &object::generate_signer_for_extending(&multisig_wallet.extend_ref); + + update_config( + multisig_signer, + vector[addr1, addr2, addr3, addr3], + option::none(), + option::none(), + option::none(), + 3 + ); + } + + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + #[expected_failure(abort_code = 0x10001, location = Self)] + fun update_config_non_weighted_invalid_threshold( + account1: signer, account2: signer, account3: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 4 + ); + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + let multisig_wallet = borrow_global(multisig_addr); + let multisig_signer = + &object::generate_signer_for_extending(&multisig_wallet.extend_ref); + + update_config( + multisig_signer, + vector[addr1, addr2, addr3, addr3], + option::none(), + option::none(), + option::none(), + 3 + ); + } + + #[test(account1 = @0x101, account2 = @0x102, account3 = @0x103)] + fun update_config_non_weighted_successfully( + account1: signer, account2: signer, account3: signer + ) acquires MultisigWallet { + // create multisig wallet + let addr1 = signer::address_of(&account1); + let addr2 = signer::address_of(&account2); + let addr3 = signer::address_of(&account3); + + create_non_weighted_multisig_account( + &account1, + string::utf8(b"multisig wallet"), + vector[addr1, addr2, addr3], + 2 + ); + + let multisig_addr = get_multisig_address( + &addr1, &string::utf8(b"multisig wallet") + ); + + set_block_info(100, 100); + + // proposal 1 : active proposal with votes from addr1, addr2, addr3 + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr3]), std::bcs::to_bytes( + &3u64 + ), std::bcs::to_bytes(&option::none()), std::bcs::to_bytes( + &option::none() + ), std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + // vote on proposal 1 without execution + vote_proposal(&account1, multisig_addr, 1, true); + vote_proposal(&account2, multisig_addr, 1, false); + vote_proposal(&account3, multisig_addr, 1, true); + + // proposal 2 : expired proposal with votes from addr1, addr2, addr3 + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr3]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(1) + ); + + // vote on proposal 2 without execution + vote_proposal(&account1, multisig_addr, 2, false); + vote_proposal(&account2, multisig_addr, 2, false); + vote_proposal(&account3, multisig_addr, 2, true); + + // proposal 2 is now expired + set_block_info(100, 110); + + // proposal 3 : executed proposal with votes from addr1, addr2, addr3 + create_proposal( + &account1, + multisig_addr, + vector[@minitia_std], + vector[string::utf8(b"multisig_v2")], + vector[string::utf8(b"update_config")], + vector[vector[]], + vector[ + vector[ + std::bcs::to_bytes(&vector[addr1, addr2, addr3]), + std::bcs::to_bytes(&3u64), + std::bcs::to_bytes(&option::none()), + std::bcs::to_bytes(&option::none()) + ] + ], + option::some(99) + ); + + // vote on proposal 3 with execution + vote_proposal(&account1, multisig_addr, 3, true); + vote_proposal(&account2, multisig_addr, 3, false); + vote_proposal(&account3, multisig_addr, 3, true); + execute_proposal(&account1, multisig_addr, 3); + + // update_config + let multisig_wallet = borrow_global(multisig_addr); + let multisig_signer = + &object::generate_signer_for_extending(&multisig_wallet.extend_ref); + update_config( + multisig_signer, + vector[addr2, addr3], + option::none(), + option::none(), + option::none(), + 1 + ); + + // check if multisig wallet is updated + let updated_multisig_wallet = borrow_global(multisig_addr); + assert!( + updated_multisig_wallet.members + == vector::map( + vector[addr2, addr3], + |addr| Member { address: addr, tier: option::none() } + ), + 1 + ); + assert!(updated_multisig_wallet.threshold == 1, 1); + + // proposal 1 details changed accordingly + let updated_proposal1 = get_proposal(multisig_addr, 1); + assert!(updated_proposal1.threshold == 1, 1); + assert!(updated_proposal1.total_weight == 2, 1); + assert!( + !simple_map::contains_key( + &updated_proposal1.votes, &Member { address: addr1, tier: option::none() } + ), + 1 + ); + assert!( + simple_map::contains_key( + &updated_proposal1.votes, &Member { address: addr2, tier: option::none() } + ), + 1 + ); + assert!( + simple_map::contains_key( + &updated_proposal1.votes, &Member { address: addr3, tier: option::none() } + ), + 1 + ); + + // proposal 2 (expired) details remain unchanged + let updated_proposal2 = get_proposal(multisig_addr, 2); + assert!(updated_proposal2.threshold == 2, 1); + assert!(updated_proposal2.total_weight == 3, 1); + assert!( + simple_map::contains_key( + &updated_proposal2.votes, &Member { address: addr1, tier: option::none() } + ), + 1 + ); + assert!( + simple_map::contains_key( + &updated_proposal2.votes, &Member { address: addr2, tier: option::none() } + ), + 1 + ); + assert!( + simple_map::contains_key( + &updated_proposal2.votes, &Member { address: addr3, tier: option::none() } + ), + 1 + ); + + // proposal 3 (executed) details remain unchanged + let updated_proposal3 = get_proposal(multisig_addr, 3); + assert!(updated_proposal3.threshold == 2, 1); + assert!(updated_proposal3.total_weight == 3, 1); + assert!( + simple_map::contains_key( + &updated_proposal3.votes, &Member { address: addr1, tier: option::none() } + ), + 1 + ); + assert!( + simple_map::contains_key( + &updated_proposal3.votes, &Member { address: addr2, tier: option::none() } + ), + 1 + ); + assert!( + simple_map::contains_key( + &updated_proposal3.votes, &Member { address: addr3, tier: option::none() } + ), + 1 + ); + } +}