diff --git a/common/constants.mligo b/common/constants.mligo deleted file mode 100644 index 6a247da61861b5c5190fd11fccd780b5c7b0c347..0000000000000000000000000000000000000000 --- a/common/constants.mligo +++ /dev/null @@ -1 +0,0 @@ -let no_operation : operation list = [] diff --git a/common/errors.mligo b/common/errors.mligo deleted file mode 100644 index 2772c773ad58132c9194e089caf29558e9e01acc..0000000000000000000000000000000000000000 --- a/common/errors.mligo +++ /dev/null @@ -1,15 +0,0 @@ -[@no_mutation] -let only_signer = "Only one of the contract signer can create an proposal" - -[@no_mutation] -let amount_must_be_zero_tez = "You must not send Tezos to the smart contract" - -[@no_mutation] -let no_proposal_exist = "No proposal exists for this counter" - -[@no_mutation] -let has_already_signed = "You have already signed this proposal" - -[@no_mutation] -let unknown_contract_entrypoint = - "Cannot connect to the target transfer token entrypoint" diff --git a/deploy/deploy.ts b/deploy/deploy.ts index e0b37982a5f69c7df56ff73b83723cb3f09eeaea..66d994f3c3bd9241795f5b34c2f69f87da41c303 100644 --- a/deploy/deploy.ts +++ b/deploy/deploy.ts @@ -65,26 +65,13 @@ const Tezos = new TezosToolkit(rpcUrl); const signer = new InMemorySigner(pk); Tezos.setProvider({ signer: signer }); -const signers: Array<string> = [ - "tz1KeYsjjSCLEELMuiq1oXzVZmuJrZ15W4mv", - "tz1MBWU1WkszFfkEER2pgn4ATKXE9ng7x1sR", - "tz1TDZG4vFoA2xutZMYauUnS4HVucnAGQSpZ", - "tz1fi3AzSELiXmvcrLKrLBUpYmq1vQGMxv9p", - "tz1go7VWXhhkzdPMSL1CD7JujcqasFJc2hrF", -]; - async function deploy() { const storage = { - metadata: MichelsonMap.fromLiteral({ - "": buf2hex(Buffer.from("tezos-storage:contents")), - contents: buf2hex(Buffer.from(JSON.stringify(metadata))), - }), - proposal_map: new MichelsonMap(), - proposal_counter: 0, - title: "dao", - governanceToken: "KT1LnPY3excYVUTLBuCfBbf1hLeGJTLhXNSz", - admin: "tz1dV9UkbS6uMuKjQy5Vs7FSptKz8LR28oFq", - token_id: 0, + name: "DAO", + description: "Dao description", + token_address: "KT1LnPY3excYVUTLBuCfBbf1hLeGJTLhXNSz", + admin_address: "tz1fd4mdbyg2VnB29pMnt4PwkQWoPzxoWzAk", + proposal_map: {}, }; try { diff --git a/fa2/errors.mligo b/fa2/errors.mligo deleted file mode 100644 index 94fb56b5ba74c1e562c3a9df8741e53d97470e5f..0000000000000000000000000000000000000000 --- a/fa2/errors.mligo +++ /dev/null @@ -1,29 +0,0 @@ -[@no_mutation] -let undefined_token = "FA2_TOKEN_UNDEFINED" - -[@no_mutation] -let ins_balance = "FA2_INSUFFICIENT_BALANCE" - -[@no_mutation] -let no_transfer = "FA2_TX_DENIED" - -[@no_mutation] -let not_owner = "FA2_NOT_OWNER" - -[@no_mutation] -let not_operator = "FA2_NOT_OPERATOR" - -[@no_mutation] -let not_supported = "FA2_OPERATORS_UNSUPPORTED" - -[@no_mutation] -let rec_hook_fail = "FA2_RECEIVER_HOOK_FAILED" - -[@no_mutation] -let send_hook_fail = "FA2_SENDER_HOOK_FAILED" - -[@no_mutation] -let rec_hook_undef = "FA2_RECEIVER_HOOK_UNDEFINED" - -[@no_mutation] -let send_hook_under = "FA2_SENDER_HOOK_UNDEFINED" diff --git a/fa2/fa2.mligo b/fa2/fa2.mligo deleted file mode 100644 index 723c4e11cea703bdae105093f15722d905d953a3..0000000000000000000000000000000000000000 --- a/fa2/fa2.mligo +++ /dev/null @@ -1,308 +0,0 @@ -(** - This file implement the TZIP-12 protocol (a.k.a FA2) for NFT on Tezos - copyright Wulfman Corporation 2021 -*) - -#import "errors.mligo" "Errors" -module Operators = struct - type owner = address - - type operator = address - - type token_id = nat - - type t = ((owner * operator), token_id set) big_map - - (** if transfer policy is Owner_or_operator_transfer *) - - let assert_authorisation (operators : t) (from_ : address) (token_id : nat) - : unit = - let sender_ = (Tezos.get_sender ()) in - if (sender_ = from_) - then () - else - let authorized = - match Big_map.find_opt (from_, sender_) operators with - Some (a) -> a - | None -> Set.empty in - if Set.mem token_id authorized then () else failwith Errors.not_operator - - (** if transfer policy is Owner_transfer - let assert_authorisation (operators : t) (from_ : address) : unit = - let sender_ = (Tezos.get_sender ()) in - if (sender_ = from_) then () - else failwith Errors.not_owner -*) - (** if transfer policy is No_transfer - let assert_authorisation (operators : t) (from_ : address) : unit = - failwith Errors.no_owner -*) - - [@no_mutation] - let assert_update_permission (owner : owner) : unit = - assert_with_error - (owner = (Tezos.get_sender ())) - "The sender can only manage operators for his own token" - - (** For an administator - let admin = tz1.... in - assert_with_error ((Tezos.get_sender ()) = admiin) "Only administrator can manage operators" - *) - - let add_operator - (operators : t) - (owner : owner) - (operator : operator) - (token_id : token_id) - : t = - if owner = operator - then operators - (* assert_authorisation always allow the owner so this case is not relevant *) - - else - let () = assert_update_permission owner in - let auth_tokens = - match Big_map.find_opt (owner, operator) operators with - Some (ts) -> ts - | None -> Set.empty in - let auth_tokens = Set.add token_id auth_tokens in - Big_map.update (owner, operator) (Some auth_tokens) operators - - [@no_mutation] - let remove_operator - (operators : t) - (owner : owner) - (operator : operator) - (token_id : token_id) - : t = - if owner = operator - then operators - (* assert_authorisation always allow the owner so this case is not relevant *) - - else - let () = assert_update_permission owner in - let auth_tokens = - match Big_map.find_opt (owner, operator) operators with - None -> None - | Some (ts) -> - let ts = Set.remove token_id ts in - if (Set.cardinal ts = 0n) then None else Some (ts) in - Big_map.update (owner, operator) auth_tokens operators - -end - -module Ledger = struct - type token_id = nat - - type owner = address - - type t = (token_id, owner) big_map - - let is_owner_of (ledger : t) (token_id : token_id) (owner : address) : bool = - (** We already sanitized token_id, a failwith here indicated a patological storage *) - - let current_owner = Option.unopt (Big_map.find_opt token_id ledger) in - current_owner = owner - - let assert_owner_of (ledger : t) (token_id : token_id) (owner : address) - : unit = - assert_with_error (is_owner_of ledger token_id owner) Errors.ins_balance - - let transfer_token_from_user_to_user - (ledger : t) - (token_id : token_id) - (from_ : owner) - (to_ : owner) - : t = - let () = assert_owner_of ledger token_id from_ in - let ledger = Big_map.update token_id (Some to_) ledger in - ledger - -end - -module TokenMetadata = struct - (** - This should be initialized at origination, conforming to either - TZIP-12 : https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-12/tzip-12.md#token-metadata - or TZIP-16 : https://gitlab.com/tezos/tzip/-/blob/master/proposals/tzip-12/tzip-12.md#contract-metadata-tzip-016 - *) - - type data = - { - token_id : nat; - token_info : (string, bytes) map - } - - type t = (nat, data) big_map - -end - -module Storage = struct - type token_id = nat - - type t = - { - ledger : Ledger.t; - token_metadata : TokenMetadata.t; - operators : Operators.t - } - - let is_owner_of (s : t) (owner : address) (token_id : token_id) : bool = - Ledger.is_owner_of s.ledger token_id owner - - let assert_token_exist (s : t) (token_id : nat) : unit = - let _ = - Option.unopt_with_error - (Big_map.find_opt token_id s.token_metadata) - Errors.undefined_token in - () - - let set_ledger (s : t) (ledger : Ledger.t) = {s with ledger = ledger} - - let get_operators (s : t) = s.operators - - let set_operators (s : t) (operators : Operators.t) = - {s with operators = operators} - -end - -type storage = Storage.t - -(** Transfer entrypoint *) - -type atomic_trans = - [@layout comb] - { - to_ : address; - token_id : nat - } - -type transfer_from = - { - from_ : address; - tx : atomic_trans list - } - -type transfer = transfer_from list - -[@entry] -let transfer : transfer -> storage -> operation list * storage = - fun (t : transfer) - (s : storage) -> (* This function process the "tx" list. Since all transfer share the same "from_" address, we use a se *) - - let process_atomic_transfer - (from_ : address) - (ledger, t : Ledger.t * atomic_trans) = - let { - to_; - token_id - } = t in - let () = Storage.assert_token_exist s token_id in - let () = Operators.assert_authorisation s.operators from_ token_id in - let ledger = - Ledger.transfer_token_from_user_to_user ledger token_id from_ to_ in - ledger in - let process_single_transfer (ledger, t : Ledger.t * transfer_from) = - let { - from_; - tx - } = t in - let ledger = List.fold_left (process_atomic_transfer from_) ledger tx in - ledger in - let ledger = List.fold_left process_single_transfer s.ledger t in - let s = Storage.set_ledger s ledger in - ([] : operation list), s - -type request = - { - owner : address; - token_id : nat - } - -type callback = - [@layout comb] - { - request : request; - balance : nat - } - -type balance_of = - [@layout comb] - { - requests : request list; - callback : callback list contract - } - -(** Balance_of entrypoint *) - -[@entry] -let balance_of : balance_of -> storage -> operation list * storage = - fun (b : balance_of) - (s : storage) -> let { - requests; - callback - } = b in - let get_balance_info (request : request) : callback = - let { - owner; - token_id - } = request in - let () = Storage.assert_token_exist s token_id in - [@no_mutation] - let balance_ = if Storage.is_owner_of s owner token_id then 1n else 0n in - { - request = request; - balance = balance_ - } in - let callback_param = List.map get_balance_info requests in - [@no_mutation] - let operation = Tezos.transaction callback_param 0mutez callback in - ([operation] : operation list), s - -(** Update_operators entrypoint *) - -type operator = - [@layout comb] - { - owner : address; - operator : address; - token_id : nat - } - -type unit_update = -| Add_operator of operator -| Remove_operator of operator - -type update_operators = unit_update list - -[@entry] -let update_operators : update_operators -> storage -> operation list * storage = - fun (updates : update_operators) - (s : storage) -> let update_operator - (operators, update : Operators.t * unit_update) = - match update with - Add_operator - { - owner = owner; - operator = operator; - token_id = token_id - } -> Operators.add_operator operators owner operator token_id - | Remove_operator - { - owner = owner; - operator = operator; - token_id = token_id - } -> Operators.remove_operator operators owner operator token_id in - let operators = Storage.get_operators s in - let operators = List.fold_left update_operator operators updates in - let s = Storage.set_operators s operators in - ([] : operation list), s - -(** If transfer_policy is No_transfer or Owner_transfer -[@entry] -let update_operators : update_operators -> storage -> operation list * storage = - fun (updates: update_operators) (s: storage) -> - let () = failwith Errors.not_supported in - ([]: operation list),s -*) - diff --git a/src/conditions.jsligo b/src/conditions.jsligo index 45f5ca5fa944cdb95f0352c5a1c6989ab09de1fc..1f94db2e8d6780a3c320fe43e6aceacf3e8a5792 100644 --- a/src/conditions.jsligo +++ b/src/conditions.jsligo @@ -1,7 +1,5 @@ -#import "../common/errors.mligo" "Errors" - #import "storage.jsligo" "Storage" @inline export const amount_must_be_zero_tez = (an_amout: tez): unit => - Assert.Error.assert(an_amout == (0 as tez), Errors.amount_must_be_zero_tez); + Assert.Error.assert(an_amout == (0 as tez), "Amount must be zero"); diff --git a/src/contract.jsligo b/src/contract.jsligo index c41fccf0f24c85b8ebeb4567bf0c5a23f75b3778..cd06c12b7cbbc5128fc09f2ffa1c3b2ca36dbfd8 100644 --- a/src/contract.jsligo +++ b/src/contract.jsligo @@ -1,123 +1,162 @@ -#import "../common/constants.mligo" "Constants" - #import "parameter.jsligo" "Parameter" #import "storage.jsligo" "Storage" #import "conditions.jsligo" "Conditions" -#import "./contracts/fa2.jsligo" "FA2" - -#import "vote.jsligo" "Vote" +#import "./type.jsligo" "Types" -#import "@ligo/fa/lib/fa2/asset/single_asset.impl.jsligo" "SingleAsset" +#import "./contracts/fa2.jsligo" "FA2" namespace Preamble { export const prepare_new_proposal = ( params: Parameter.Types.proposal_params, - storage: Storage.Types.t - ): Storage.Types.proposal => { + storage: Storage.Utils.storage + ): Types.proposal => { Conditions.amount_must_be_zero_tez((Tezos.get_amount())); - return Storage.Utils.create_proposal(params); + return Storage.Utils.create_proposal(params, storage); }; export const retrieve_a_proposal = ( proposal_number: nat, - storage: Storage.Types.t - ): Storage.Types.proposal => { - const target_proposal: Storage.Types.proposal = + storage: Storage.Utils.storage + ): Types.proposal => { + const target_proposal: Types.proposal = Storage.Utils.retrieve_proposal(proposal_number, storage); return target_proposal; }; }; // =============================================================================================== -type request = [Parameter.Types.t, Storage.Types.t]; - -export type result = [list<operation>, Storage.Types.t]; - -export type t = address - -export type fa2TransferContract = contract<SingleAsset.TZIP12.transfer> +export type result = [list<operation>, Storage.Utils.storage]; @entry export const create_proposal = ( params: Parameter.Types.proposal_params, - storage: Storage.Types.t + storage: Storage.Utils.storage ): result => { const proposal = Preamble.prepare_new_proposal(params, storage); + const buyer_trans: Types.atomic_trans = { + to_: Tezos.get_self_address(), + token_id: 0n, + amount: 10n, + } + const transfer: Types.transfer = + list([{ from_: Tezos.get_sender(), txs: list([buyer_trans]) }]); + const tokenOperation = FA2.send(transfer, storage.token_address); const final_storage = Storage.Utils.register_proposal(proposal, storage); - return [Constants.no_operation, final_storage]; + return [list([tokenOperation]), final_storage]; }; @entry export const vote_proposal = ( vote_params: Parameter.Types.vote_params, - storage: Storage.Types.t, + storage: Storage.Utils.storage ): result => { let proposal = Preamble.retrieve_a_proposal(vote_params.proposal_number, storage); - proposal = Storage.Utils.add_vote_to_proposal(proposal, vote_params.vote); - const final_storage = - Storage.Utils.update_proposal( + const amount = Tezos.get_amount(); + if (amount > 0mutez) failwith("This entrypoint doesn't accept tez"); + if (proposal.status != Ongoing()) failwith("Proposal is not ongoing"); + const buyer_transfer: Types.atomic_trans = { + to_: Tezos.get_self_address(); + token_id: 0n; + amount: vote_params.token_amount; + } + const transferObj: Types.transfer = + list([{ from_: Tezos.get_sender(), txs: list([buyer_transfer]) }]); + const vote_operation = FA2.send(transferObj, storage.token_address); + let new_proposal_votes = proposal.votes; + for (let i = 0n; i < vote_params.token_amount; i += 1n) + new_proposal_votes = List.cons(vote_params.vote, new_proposal_votes); + let new_proposal = { ...proposal, votes: new_proposal_votes }; + const new_storage = { + ...storage, + proposal_map: Map.add( vote_params.proposal_number, - proposal, - storage - ); - const operations = FA2.perform_operations(proposal); - return [operations, final_storage]; + new_proposal, + storage.proposal_map + ) + }; + return [list([vote_operation]), new_storage]; }; @entry -const buy_token = (token_amount: nat, store: Storage.Types.t): result => { +const buy_token = (token_amount: nat, store: Storage.Utils.storage): result => { const total_cost = 1000mutez * token_amount; if (Tezos.get_amount() != total_cost) { - failwith("Incorrect amount of XTZ sent"); - } - const getTransferEntrypoint = (addr: address): fa2TransferContract => { - return Tezos.get_entrypoint("%transfer", addr); + failwith("Incorrect amount of tez sent"); } - const transfer = ( - { token_addr, from_, to_, amount_, token_id }: { - token_addr: address, - from_: address, - to_: address, - amount_: nat, - token_id: nat - } - ): operation => { - const dest = getTransferEntrypoint(token_addr); - const transfer_requests: SingleAsset.TZIP12.transfer = - list( - [ - { - from_: from_, - txs: list( - [ - { - to_: to_, - amount: amount_, - token_id: token_id as nat - } - ] - ) - } - ] - ); - return Tezos.Next.Operation.transaction( - transfer_requests, - (0 as tez), - dest - ) + const buyer_transfer: Types.atomic_trans = { + to_: Tezos.get_sender(); + token_id: 0n; + amount: token_amount; } - const operation: operation = - transfer( - { - token_addr: store.governanceToken, - from_: store.governanceToken, - to_: Tezos.get_sender(), // (1) Transfer to self (minting - amount_: token_amount, - token_id: 0n - } - ); + const transferObj: Types.transfer = + list([{ from_: store.admin_address, txs: list([buyer_transfer]) }]); + const operation = FA2.send(transferObj, store.token_address); return [list([operation]), store]; }; + +@entry +function resolve_proposal(proposal_id: nat, store: Storage.Utils.storage): result { + const amount = Tezos.get_amount(); + if (amount > 0mutez) failwith("This entrypoint doesn't accept tez"); + let requested_proposal: Types.proposal = + Storage.Utils.retrieve_proposal(proposal_id, store); + if ( + requested_proposal.owner != Tezos.get_sender() + || Tezos.get_sender() != store.admin_address + ) failwith("Sender has not resolve right for this proposal"); + let yayCounter: nat = 0n; + let nopeCounter: nat = 0n; + for (const vote of requested_proposal.votes) { + if (vote == Yay()) { + yayCounter += 1n; + } else if (vote == Nope()) { + nopeCounter += 1n; + } + } + const new_proposal = + (yayCounter > nopeCounter) ? { + ...requested_proposal, status: Accepted() + } : { ...requested_proposal, status: Rejected() }; + const new_storage = { + ...store, + proposal_map: Map.add(proposal_id, new_proposal, store.proposal_map) + } + return [list([]), new_storage]; +} + +@view +const get_dao_infos = (_arg: unit, store: Storage.Utils.storage): Storage. +Utils. +storage => + store; + +@view +function ongoing_proposals(_arg: unit, store: Storage.Utils.storage): list< + Types.proposal +> { + let proposalsList: list<Types.proposal> = list([]); + for (const [_key, val] of store.proposal_map) { + if (val.status == Ongoing()) { + proposalsList = List.cons(val, proposalsList); + } + } + return proposalsList; +} + +@view +function get_all_proposals(_arg: unit, store: Storage.Utils.storage): list< + Types.proposal +> { + let proposalsList: list<Types.proposal> = list([]); + for (const [_key, val] of store.proposal_map) { + proposalsList = List.cons(val, proposalsList); + } + return proposalsList; +} + +@view +const get_fa2_address = (_arg: unit, store: Storage.Utils.storage): address => + store.token_address; diff --git a/src/contracts/fa2.jsligo b/src/contracts/fa2.jsligo index ea1485828a6845d0f79724b402a52cd37f0f157d..562c8ba96849408550f25838c6c8fd662822fd35 100644 --- a/src/contracts/fa2.jsligo +++ b/src/contracts/fa2.jsligo @@ -1,31 +1,13 @@ -#import "../../common/errors.mligo" "Errors" +#import "../type.jsligo" "Types" -#import "../../common/constants.mligo" "Constants" - -#import "../storage.jsligo" "Storage" - -#import "@ligo/fa/lib/fa2/asset/single_asset.impl.jsligo" "FA2" - -export const send = ( - transfers: FA2.TZIP12.transfer, - target_fa2_address: address -): operation => { +export const send = (transfers: Types.transfer, target_address: address): operation => { @no_mutation - const fa2_contract_opt: option<contract<FA2.TZIP12.transfer>> = - Tezos.get_entrypoint_opt("%transfer", target_fa2_address); - return match(fa2_contract_opt) { + const contract_opt: option<contract<Types.transfer>> = + Tezos.get_entrypoint_opt("%transfer", target_address); + return match(contract_opt) { when (Some(contr)): - Tezos.Next.Operation.transaction(transfers, 0 as tez, contr) + Tezos.Next.Operation.transaction(transfers, 0mutez, contr) when (None()): - failwith(Errors.unknown_contract_entrypoint) + failwith("No contract at address") }; } - -export const perform_operations = (proposal: Storage.Types.proposal): list< - operation -> => { - if (proposal.executed) { - return list([send(proposal.transfers, proposal.fa2_address)]) as - list<operation>; - } else return Constants.no_operation; -} diff --git a/src/parameter.jsligo b/src/parameter.jsligo index 2f61d30615e1a0e189b440f724dbd9a16b59e430..8345a2affcea3f20e8e9fa296f236475716ca582 100644 --- a/src/parameter.jsligo +++ b/src/parameter.jsligo @@ -1,18 +1,13 @@ -#import "@ligo/fa/lib/fa2/asset/single_asset.impl.jsligo" "FA2" +#import "./type.jsligo" "Types" export namespace Types { - export type proposal_params = { - id: nat, - transfers: FA2.TZIP12.transfer, - title: string, - startAt: timestamp, - endAt: timestamp, - description: string, - fa2_address: address, - }; - export type vote = ["Yay"] | ["Nope"] | ["Pass"]; + export type proposal_params = { title: string, description: string, }; export type proposal_number = nat; - export type vote_params = { proposal_number: proposal_number, vote: vote, }; + export type vote_params = { + proposal_number: nat, + token_amount: nat, + vote: Types.vote + }; export type t = ["Create_proposal", proposal_params] | ["Sign_proposal", vote_params]; }; diff --git a/src/storage.jsligo b/src/storage.jsligo index 1483c937724c416b7ebf2ea819c4a077e927b7d4..ec653beee5170cfdf94bd49c921bebce7546ad12 100644 --- a/src/storage.jsligo +++ b/src/storage.jsligo @@ -1,123 +1,58 @@ #import "parameter.jsligo" "Parameter" -#import "../common/errors.mligo" "Errors" - -#import "vote.jsligo" "Vote" - -#import "@ligo/fa/lib/fa2/asset/single_asset.impl.jsligo" "FA2" - -export namespace Types { - export type status = ["Pending"] | ["Rejected"] | ["Accepted"]; - export type proposal = { - id: nat, - title: string, - votes: list<Parameter.Types.vote>, - executed: bool, - transfers: FA2.TZIP12.transfer, - startAt: timestamp, - endAt: timestamp, - creator: address, - description: string, - fa2_address: address, - status: status - }; - export type t = { - title: string, - governanceToken: address, - admin: address, - proposal_counter: nat, - proposal_map: big_map<nat, proposal>, - token_id: nat, - }; -}; +#import "./type.jsligo" "Types" export namespace Utils { + export type storage = Types.dao; + export type result = [list<operation>, storage]; @inline - const new_storage = ( - [title, governanceToken, admin, token_id]: [ - string, - address, - address, - nat - ] - ): Types.t => - ( - { - proposal_counter: 0 as nat, - proposal_map: Big_map.empty as big_map<nat, Types.proposal>, - title: title, - governanceToken: governanceToken, - admin: admin, - token_id: token_id; - } - ); - @inline - export const create_proposal = (params: Parameter.Types.proposal_params): Types. - proposal => { - const proposal: Types.proposal = { - id: params.id, + export const create_proposal = ( + params: Parameter.Types.proposal_params, + store: storage + ): Types.proposal => { + const amount = Tezos.get_amount(); + if (amount > 0mutez) { + failwith("You must not send tez to the contract"); + }; + const id: nat = Map.size(store.proposal_map) + 1n; + let proposal: Types.proposal = { + id: id, title: params.title, - executed: false, - transfers: params.transfers, - startAt: params.startAt, - endAt: params.endAt, - creator: Tezos.get_sender(), - votes: list([]) as list<Parameter.Types.vote>, description: params.description, - fa2_address: params.fa2_address, - status: Pending() - }; + votes: list([]), + status: Ongoing(), + creation_timestamp: Tezos.get_now(), + owner: Tezos.get_sender(), + } return proposal; }; @inline export const register_proposal = ( proposal: Types.proposal, - storage: Types.t - ): Types.t => { - @no_mutation - const proposal_counter = storage.proposal_counter + (1 as nat); - const proposal_map = - Big_map.add(proposal_counter, proposal, storage.proposal_map); - return { - ...storage, - proposal_map: proposal_map, - proposal_counter: proposal_counter - }; + storage: Types.dao + ): Types.dao => { + const proposal_counter = Map.size(storage.proposal_map) + 1n; + const new_proposal_map: map<nat, Types.proposal> = + Map.add(proposal_counter, proposal, storage.proposal_map); + return { ...storage, proposal_map: new_proposal_map } }; @inline - export const retrieve_proposal = (proposal_number: nat, storage: Types.t): Types. + export const retrieve_proposal = (proposal_id: nat, store: storage): Types. proposal => - match(Big_map.find_opt(proposal_number, storage.proposal_map)) { + match(Map.find_opt(proposal_id, store.proposal_map)) { when (None()): - failwith(Errors.no_proposal_exist) + failwith("Proposal not found") when (Some(proposal)): proposal }; @inline - export const add_vote_to_proposal = ( - proposal: Types.proposal, - vote: Parameter.Types.vote - ): Types.proposal => { - const executed: bool = proposal.executed; - @no_mutation - return { - ...proposal, - votes: List.cons(vote, proposal.votes) as list<Parameter.Types.vote>, - executed: executed - }; - }; - @inline export const update_proposal = ( proposal_number: Parameter.Types.proposal_number, proposal: Types.proposal, - storage: Types.t - ): Types.t => { + store: storage + ): storage => { const proposal_map = - Big_map.update( - proposal_number, - Some(proposal), - storage.proposal_map - ); - return { ...storage, proposal_map: proposal_map } + Map.update(proposal_number, Some(proposal), store.proposal_map); + return { ...store, proposal_map: proposal_map } }; } diff --git a/src/type.jsligo b/src/type.jsligo new file mode 100644 index 0000000000000000000000000000000000000000..4cf7e8aa7d2a3aabd35fdb28ee85dbb43e59e64c --- /dev/null +++ b/src/type.jsligo @@ -0,0 +1,29 @@ +export type proposal_status = ["Ongoing"] | ["Accepted"] | ["Rejected"] + +export type vote = ["Yay"] | ["Nope"] | ["Pass"] + +export type proposal = { + id: nat, + title: string, + description: string, + votes: list<vote>, + status: proposal_status, + creation_timestamp: timestamp, + owner: address, +} + +export type dao = { + name: string, + description: string, + token_address: address, + admin_address: address, + proposal_map: map<nat, proposal>, +} + +export type atomic_trans = + @layout("comb") + { amount: nat; token_id: nat, to_: address; } + +export type transfer_from = { from_: address; txs: list<atomic_trans> }; + +export type transfer = list<transfer_from>; diff --git a/src/vote.jsligo b/src/vote.jsligo deleted file mode 100644 index fa65ac1b3f39c40b67d5d330e0b59f82f01d1309..0000000000000000000000000000000000000000 --- a/src/vote.jsligo +++ /dev/null @@ -1,22 +0,0 @@ -export type choice = string - -export type t = [choice, nat] - -export type votes = map<address, t> - -export const count = (votes: votes): [nat, nat, nat, nat] => { - const sum = ([acc, vote]: [[nat, nat, nat], [address, t]]): [nat, nat, nat] => { - const [yay, nope, pass] = acc; - const [_, [choice, nb]] = vote; - if (choice == "yay") { - return [yay + nb, nope, pass]; - } else if (choice == "nope") { - return [yay, nope + nb, pass]; - } else if (choice == "pass") { - return [yay, nope, pass + nb]; - } - return acc; - }; - const [yay, nope, pass] = Map.fold(sum, votes, [0 as nat, 0 as nat, 0 as nat]); - return [yay + nope + pass, yay, nope, pass]; -}; diff --git a/test/Multisig.tz b/test/Multisig.tz deleted file mode 100644 index 9d98b74a6bb6a04a4e4a1e0d1d5ddcc4a52492f5..0000000000000000000000000000000000000000 --- a/test/Multisig.tz +++ /dev/null @@ -1,229 +0,0 @@ -{ parameter - (or (pair %create_operation - (pair (address %target_fa12) (address %target_to)) - (nat %token_amount)) - (nat %sign)) ; - storage - (pair (pair (nat %operation_counter) - (big_map %operation_map - nat - (pair (pair (pair (set %approved_signers address) (bool %executed)) - (pair (address %target_fa12) (address %target_to))) - (pair (timestamp %timestamp) (nat %token_amount))))) - (pair (set %signers address) (nat %threshold))) ; - code { PUSH string "Only one of the contract signer can create an operation" ; - NIL operation ; - DUP 3 ; - CDR ; - DIG 3 ; - CAR ; - IF_LEFT - { SWAP ; - DIG 3 ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - SENDER ; - MEM ; - IF { DROP } { FAILWITH } ; - PUSH string "You must not send Tezos to the smart contract" ; - PUSH mutez 0 ; - AMOUNT ; - COMPARE ; - EQ ; - IF { DROP } { FAILWITH } ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - NOW ; - PAIR ; - DUP 3 ; - CAR ; - CDR ; - DIG 3 ; - CAR ; - CAR ; - PAIR ; - PUSH bool False ; - EMPTY_SET address ; - PAIR ; - PAIR ; - PAIR ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - DUP 3 ; - CAR ; - CDR ; - PUSH nat 1 ; - DUP 5 ; - CAR ; - CAR ; - ADD ; - PAIR ; - PAIR ; - DUP ; - CDR ; - DUP 4 ; - CAR ; - CDR ; - DIG 3 ; - DIG 4 ; - CAR ; - CAR ; - SWAP ; - SOME ; - SWAP ; - UPDATE ; - DIG 2 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - SWAP ; - PAIR } - { SWAP ; - DIG 3 ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - SENDER ; - MEM ; - IF { DROP } { FAILWITH } ; - PUSH string "You have already signed this operation" ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CAR ; - SENDER ; - MEM ; - IF { DROP } { FAILWITH } ; - DUP ; - CAR ; - CDR ; - DUP 3 ; - GET ; - IF_NONE - { PUSH string "No operation exists for this counter" ; FAILWITH } - { DUP ; - CDR ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CDR ; - DUP 3 ; - CAR ; - CAR ; - CDR ; - DIG 3 ; - CAR ; - CAR ; - CAR ; - PUSH bool True ; - SENDER ; - UPDATE ; - PAIR ; - PAIR ; - PAIR } ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - CDR ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - CAR ; - SIZE ; - COMPARE ; - GE ; - IF { DIG 3 ; - DROP ; - NIL operation ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CDR ; - CAR ; - DUP 3 ; - CAR ; - CDR ; - CDR ; - DUP 4 ; - CDR ; - CDR ; - PAIR ; - PAIR ; - DUP ; - CDR ; - CONTRACT %transfer (pair address (pair address nat)) ; - IF_NONE - { PUSH string "Cannot connect to the target transfer token entrypoint" ; - FAILWITH } - {} ; - SWAP ; - DUP ; - DUG 2 ; - CAR ; - CAR ; - DIG 2 ; - CAR ; - CDR ; - PAIR ; - SELF_ADDRESS ; - PAIR ; - SWAP ; - PUSH mutez 0 ; - DIG 2 ; - TRANSFER_TOKENS ; - CONS ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - DUP 3 ; - CAR ; - CDR ; - PUSH bool True ; - DIG 4 ; - CAR ; - CAR ; - CAR ; - PAIR ; - PAIR ; - PAIR ; - SWAP ; - PAIR } - { DIG 3 ; PAIR } ; - SWAP ; - DUP ; - DUG 2 ; - CDR ; - DUP 3 ; - CAR ; - CDR ; - DUP 3 ; - CDR ; - SOME ; - DIG 5 ; - UPDATE ; - DIG 3 ; - CAR ; - CAR ; - PAIR ; - PAIR ; - SWAP ; - CAR ; - PAIR } } } - diff --git a/test/multisig.test.jsligo b/test/multisig.test.jsligo deleted file mode 100644 index c47447b325b786f2d54403472404f6ffcb09d906..0000000000000000000000000000000000000000 --- a/test/multisig.test.jsligo +++ /dev/null @@ -1,368 +0,0 @@ -#import "../fa2/fa2.mligo" "FA2" - -#import "../src/contract.jsligo" "Main" - -#import "../src/storage.jsligo" "Storage" - -#import "../src/parameter.jsligo" "Parameter" - -import Types = Parameter.Types; - -const assert_error = (res: test_exec_result, expected_error: string): unit => { - const expected_error2 = Test.eval(expected_error); - match(res) { - when (Fail(x)): - do { - match(x) { - when (Rejected(x)): - assert(Test.michelson_equal(x[0], expected_error2)) - when (Balance_too_low(_r)): - failwith("contract failed: balance too low") - when (Other(s)): - failwith(s) - } - } - when (Success(_n)): - failwith("Expected to fail but passed") - }; -}; - -const x = Test.reset_state(7 as nat, list([30000000 as tez]) as list<tez>); - -const alice: address = Test.nth_bootstrap_account(1); - -const bob: address = Test.nth_bootstrap_account(2); - -const charly: address = Test.nth_bootstrap_account(3); - -const delta: address = Test.nth_bootstrap_account(4); - -const echo: address = Test.nth_bootstrap_account(5); - -const unknown = Test.nth_bootstrap_account(6); - -const signers: set<address> = - Set.add( - alice, - Set.add( - bob, - Set.add( - charly, - Set.add(delta, Set.add(echo, Set.empty as set<address>)) - ) - ) - ); - -const threshold: nat = 3 as nat; - -const proposal_map: big_map<nat, Storage.Types.proposal> = Big_map.empty; - -const proposal_counter: nat = 0 as nat; - -const metadata_initial: big_map<string, bytes> = Big_map.empty; - -const storage: Storage.Types.t = ( - { - signers: signers, - threshold: threshold, - proposal_map: proposal_map, - proposal_counter: proposal_counter, - metadata: metadata_initial - } -); - -const atomic: FA2.atomic_trans = { to_: bob, token_id: 1 as nat }; - -const transfer_from: FA2.transfer_from = { - from_: alice, - tx: list([atomic]) as list<FA2.atomic_trans> -}; - -const transfers: FA2.transfer = list([transfer_from]) as FA2.transfer; - -const create_proposal_params: Parameter.Types.proposal_params = { - target_fa2: bob as address, - transfers: transfers -}; - -const test_signer_creates_proposal_works = do { - const orig = Test.originate(contract_of(Main), storage, 0 as tez); - Test.set_source(alice); - const expected_proposal: Storage.Types.proposal = { - approved_signers: Set.add(alice, Set.empty as set<address>), - executed: false, - number_of_signer: 1 as nat, - target_fa2: bob as address, - transfers: transfers, - timestamp: 0 as timestamp - }; - const gas_used: nat = - Test.transfer_exn( - orig.addr, - Create_proposal(create_proposal_params), - 0 as mutez - ); - Test.log(gas_used); - const end_storage = Test.get_storage(orig.addr); - const end_proposal: Storage.Types.proposal = - match(Big_map.find_opt(1 as nat, end_storage.proposal_map)) { - when (Some(op)): - op - when (None()): - failwith(Conditions.Errors.no_proposal_exist) - }; - const final_proposal: Storage.Types.proposal = { - ...end_proposal, timestamp: (0 as timestamp) - }; - assert(final_proposal == expected_proposal); -}; - -export const test_unknown_creates_proposal_fails = do { - const orig = Test.originate(contract_of(Main), storage, 0 as tez); - Test.set_source(unknown); - const fail_tx = - Test.transfer( - orig.addr, - Create_proposal(create_proposal_params), - 0 as tez - ); - assert_error(fail_tx, Conditions.Errors.only_signer); - const curr_storage = Test.get_storage(orig.addr); - assert(curr_storage == storage); -}; - -const test_signer_signs_proposal_works = do { - const init_proposal: Storage.Types.proposal = { - approved_signers: Set.add(alice, Set.empty as set<address>), - executed: false, - number_of_signer: 1 as nat, - target_fa2: bob as address, - transfers: transfers, - timestamp: 5 as timestamp, - }; - const orig = - Test.originate( - contract_of(Main), - { - ...storage, - proposal_map: Big_map.literal(list([[1 as nat, init_proposal]])) - }, - 0 as tez - ); - Test.set_source(bob); - const _gas_used: nat = - Test.transfer_exn( - orig.addr, - (Sign_proposal(1 as nat) as parameter_of Main), - 0 as tez - ); - const curr_storage = Test.get_storage(orig.addr); - const end_signed_proposal = - match(Big_map.find_opt(1 as nat, curr_storage.proposal_map)) { - when (Some(op)): - op - when (None()): - failwith(Conditions.Errors.no_proposal_exist) - }; - const expected_signer: set<address> = - Set.add(bob, Set.add(alice, Set.empty as set<address>)); - assert(end_signed_proposal.approved_signers == expected_signer); -}; - -export const test_unknown_signs_proposal_fails = do { - const orig = Test.originate(contract_of(Main), storage, 0 as tez); - Test.set_source(unknown); - const fail_tx = - Test.transfer( - orig.addr, - (Sign_proposal(1 as nat) as parameter_of Main), - 0 as tez - ); - assert_error(fail_tx, Main.Conditions.Errors.only_signer); - const curr_storage = Test.get_storage(orig.addr); - assert(curr_storage == storage); -}; - -export const test_unknown_signs_unknown_proposal_fails = do { - const orig = Test.originate(contract_of(Main), storage, 0 as tez); - Test.set_source(unknown); - const fail_sign_tx = - Test.transfer( - orig.addr, - (Sign_proposal(2 as nat) as parameter_of Main), - 0 as tez - ); - assert_error(fail_sign_tx, Main.Conditions.Errors.only_signer); -}; - -const test_signer_signs_unknown_proposal_fails = do { - const orig = Test.originate(contract_of(Main), storage, 0 as tez); - Test.set_source(alice); - const fail_sign_tx = - Test.transfer( - orig.addr, - (Sign_proposal(2 as nat) as parameter_of Main), - 0 as tez - ); - assert_error(fail_sign_tx, Main.Conditions.Errors.no_proposal_exist); -}; - -const signer_creates_proposal_signers_sign_proposal_execute_works = do { - Test.set_source(alice); - const orig = Test.originate(contract_of(Main), storage, 0 as tez); - const addr = Test.to_address(orig.addr); - const ledger: FA2.Ledger.t = - Big_map.literal( - list([[1 as nat, alice], [2 as nat, bob], [3 as nat, charly]]) - ); - const _token_info = Map.empty as map<string, bytes>; - const token_metadata = - Big_map.literal( - list( - [ - [ - 1 as nat, - { - token_id: 1 as nat, - token_info: Map.empty as map<string, bytes> - } - ], - [ - 2 as nat, - { - token_id: 2 as nat, - token_info: Map.empty as map<string, bytes> - } - ], - [ - 3 as nat, - { - token_id: 3 as nat, - token_info: Map.empty as map<string, bytes> - } - ] - ] - ) - ) as FA2.TokenMetadata.t; - const operators: FA2.Operators.t = - Big_map.literal(list([[[alice, addr], Set.literal(list([1 as nat]))]])) as - FA2.Operators.t; - const storage_fa2 = { - ledger: ledger, - token_metadata: token_metadata, - operators: operators - }; - const orig_fa2 = Test.originate(contract_of(FA2), storage_fa2, 0 as tez); - const addr_fa2 = Test.to_address(orig_fa2.addr); - const params: Main.Parameter.Types.proposal_params = { - ...create_proposal_params, target_fa2: addr_fa2 - }; - let _gas_used: nat = - Test.transfer_exn(orig.addr, Create_proposal(params), 0 as mutez); - Test.set_source(bob); - _gas_used - = Test.transfer_exn(orig.addr, (Sign_proposal(1 as nat)), 0 as tez); - Test.set_source(charly); - _gas_used - = Test.transfer_exn(orig.addr, (Sign_proposal(1 as nat)), 0 as tez); - const expected_executed_signers = - Set.add(alice, Set.add(bob, Set.add(charly, Set.empty as set<address>))); - const curr_storage = Test.get_storage(orig.addr); - const end_executed_proposal = - match(Big_map.find_opt(1 as nat, curr_storage.proposal_map)) { - when (Some(op)): - op - when (None()): - failwith(Main.Conditions.Errors.no_proposal_exist) - }; - assert(end_executed_proposal.executed); - assert(end_executed_proposal.approved_signers == expected_executed_signers); - const curr_fa2_storage: FA2.storage = Test.get_storage(orig_fa2.addr); - const owner: address = - match(Big_map.find_opt(1 as nat, curr_fa2_storage.ledger)) { - when (Some(val)): - val - when (None()): - failwith(FA2.Errors.undefined_token) - }; - assert(owner == bob); -}; - -const _signer_creates_proposal_signers_sign_proposal_execute_over_threshold_works = do { - Test.set_source(alice); - const storage_init = { ...storage, threshold: 1 as nat }; - const orig = Test.originate(contract_of(Main), storage_init, 0 as tez); - const addr: address = Test.to_address(orig.addr); - const ledger: FA2.Ledger.t = - Big_map.literal( - list([[1 as nat, alice], [2 as nat, bob], [3 as nat, charly]]) - ); - const _token_info = Map.empty as map<string, bytes>; - const token_metadata = - Big_map.literal( - list( - [ - [ - 1 as nat, - { - token_id: 1 as nat, - token_info: Map.empty as map<string, bytes> - } - ], - [ - 2 as nat, - { - token_id: 2 as nat, - token_info: Map.empty as map<string, bytes> - } - ], - [ - 3 as nat, - { - token_id: 3 as nat, - token_info: Map.empty as map<string, bytes> - } - ] - ] - ) - ) as FA2.TokenMetadata.t; - const operators: FA2.Operators.t = - Big_map.literal(list([[[alice, addr], Set.literal(list([1 as nat]))]])) as - FA2.Operators.t; - const storage_fa2 = { - ledger: ledger, - token_metadata: token_metadata, - operators: operators - }; - const orig_fa2 = Test.originate(contract_of(FA2), storage_fa2, 0 as tez); - const addr_fa2 = Test.to_address(orig_fa2.addr); - const params: Main.Parameter.Types.proposal_params = { - ...create_proposal_params, target_fa2: addr_fa2 - }; - let _gas_used: nat = - Test.transfer_exn(orig.addr, Create_proposal(params), 0 as mutez); - Test.set_source(bob); - _gas_used = Test.transfer_exn(orig.addr, (Sign_proposal(1n)), 0 as tez); - const expected_executed_signers = - Set.add(alice, Set.add(bob, Set.empty as set<address>)); - const curr_storage = Test.get_storage(orig.addr); - Test.log(curr_storage); - const end_executed_proposal = - match(Big_map.find_opt(1 as nat, curr_storage.proposal_map)) { - when (Some(op)): - op - when (None()): - failwith(Main.Conditions.Errors.no_proposal_exist) - }; - assert(end_executed_proposal.executed); - assert(end_executed_proposal.approved_signers == expected_executed_signers); - const curr_fa2_storage: FA2.storage = Test.get_storage(orig_fa2.addr); - const owner: address = - match(Big_map.find_opt(1 as nat, curr_fa2_storage.ledger)) { - when (Some(val)): - val - when (None()): - failwith(FA2.Errors.undefined_token) - }; - assert(owner == bob); -}; diff --git a/test/multisig_mutation.test.jsligo b/test/multisig_mutation.test.jsligo deleted file mode 100644 index d567a7f01ef4724741ae41267fef35b436b4e6db..0000000000000000000000000000000000000000 --- a/test/multisig_mutation.test.jsligo +++ /dev/null @@ -1,64 +0,0 @@ -#import "multisig.test.jsligo" "Test_Multisig" - -/* -type main_tuple = - ( - main_multisig: Test_Multisig.func_main, - main_fa2: Test_Multisig.func_mainfa2 - ) => unit; - -const _test_mutation_complex = (func: main_tuple): unit => { - match( - Test.mutation_test( - [Test_Multisig.Main.main, Test_Multisig.FA2.main], - ([a, b]) => func(a, b) - ) - ) { - when (None()): - [] - when (Some(pmutation)): - do { Test.log(pmutation[1]); } - } -}; - -const _test_mutation_func = (func: (mainf: Test_Multisig.func_main) => unit): unit => { - match(Test.mutation_test(Test_Multisig.Main.main, func)) { - when (None()): - [] - when (Some(pmutation)): - do { - const _mut: option<string> = - Test.save_mutation("mutations", pmutation[1]); - } - }; -} - -const _tests = - list( - [ - Test_Multisig._test_signer_creates_proposal_works, - Test_Multisig._test_unknown_creates_proposal_fails, - Test_Multisig._test_signer_signs_proposal_works, - Test_Multisig._test_unknown_signs_proposal_fails, - Test_Multisig._test_unknown_signs_unknown_proposal_fails, - Test_Multisig._test_signer_signs_unknown_proposal_fails - ] - ); - -const _tests_complex = - list( - [ - Test_Multisig. - _signer_creates_proposal_signers_sign_proposal_execute_works, - Test_Multisig. - _signer_creates_proposal_signers_sign_proposal_execute_over_threshold_works - ] - ); - -const mutation_tests = (): unit => { - List.iter(_test_mutation_func, _tests); - List.iter(_test_mutation_complex, _tests_complex); -}; - -const _ = mutation_tests(); -*/ diff --git a/test/test_multisig.py b/test/test_multisig.py deleted file mode 100644 index 32c3fdb232338eab4d3370d3b6f8db1a5f4d7a40..0000000000000000000000000000000000000000 --- a/test/test_multisig.py +++ /dev/null @@ -1,158 +0,0 @@ -from unittest import TestCase -from contextlib import contextmanager -from copy import deepcopy -from pytezos import ContractInterface, MichelsonRuntimeError, pytezos -from pytezos.michelson.types.option import SomeLiteral, NoneLiteral -from typing import List, Dict - -compiled_contract_path: str = "Multisig.tz" - -initial_storage = ContractInterface.from_file(compiled_contract_path).storage.dummy() - -alice: str = 'tz1hNVs94TTjZh6BZ1PM5HL83A7aiZXkQ8ur' -bob: str = 'tz1c6PPijJnZYjKiSQND4pMtGMg6csGeAiiF' -charly: str = 'tz1fABJ97CJMSP2DKrQx2HAFazh6GgahQ7ZK' -oscar: str = 'tz1Phy92c2n817D17dUGzxNgw1qCkNSTWZY2' -fox: str = 'tz1XH5UyhRCUmCdUUbqD4tZaaqRTgGaFXt7q' -unknown: str = 'tz1UCFixZ2aZg4FJezcNtT5U6XLJ57b3yyPN' -fa12: str = 'KT1SjXiUX63QvdNMcM2m492f7kuf8JxXRLp4' -TOKEN_AMOUNT: int = 1000 - -initial_storage["signers"]: List[str] = [alice, bob, charly, oscar, fox] -initial_storage["threshold"]: int = 3 -initial_storage["operation_map"]: Dict[int, str] = dict() -initial_storage["operation_counter"]: int = 0 - -only_signer: str = "Only one of the contract signer can create an operation" -amount_must_be_zero_tez: str = "You must not send Tezos to the smart contract" -unknown_contract_entrypoint: str = "Cannot connect to the target transfer token entrypoint" -no_operation_exist: str = "No operation exists for this counter" -has_already_signed: str = "You have already signed this operation" - - -class MultiSigContractTest(TestCase): - @classmethod - def setUpClass(cls): - cls.multisig = ContractInterface.from_file(compiled_contract_path) - cls.maxDiff = None - - @contextmanager - def raisesMichelsonError(self, error_message): - with self.assertRaises(MichelsonRuntimeError) as r: - yield r - - error_msg = r.exception.format_stdout() - if "FAILWITH" in error_msg: - self.assertEqual(f"FAILWITH: '{error_message}'", r.exception.format_stdout()) - else: - self.assertEqual(f"'{error_message}': ", r.exception.format_stdout()) - - def test_create_operation_should_work(self): - """One of the signers creates a multisig operation request, so it works""" - init_storage = deepcopy(initial_storage) - params = { - "target_fa12": fa12, - "target_to": bob, - "token_amount": TOKEN_AMOUNT - } - expected_operation_request = { - "target_to": bob, - "target_fa12": fa12, - "token_amount": TOKEN_AMOUNT, - "timestamp": 0, - "approved_signers": [], - "executed": False - } - res = self.multisig.create_operation(params).interpret(storage=init_storage, sender=bob) - self.assertDictEqual(res.storage["operation_map"][0], expected_operation_request) - self.assertEqual(res.storage["operation_counter"], init_storage["operation_counter"] + 1) - self.assertEqual(res.operations, []) - - def test_create_operation_not_signer_should_not_work(self): - """A random person, not one of the signers, tries to create a multisig operation request, so it doesn't work""" - init_storage = deepcopy(initial_storage) - params = { - "target_fa12": fa12, - "target_to": bob, - "token_amount": TOKEN_AMOUNT - } - with self.raisesMichelsonError(only_signer): - self.multisig.create_operation(params).interpret(storage=init_storage, sender=unknown) - - def test_sign_operation_signer_should_work(self): - """One of the signers signs the operation request""" - init_storage = deepcopy(initial_storage) - init_storage["operation_map"][1] = { - "target_to": bob, - "target_fa12": fa12, - "token_amount": TOKEN_AMOUNT, - "timestamp": 0, - "approved_signers": [], - "executed": False - } - init_storage["operation_counter"] += 1 - res = self.multisig.sign(1).interpret(storage=init_storage, sender=alice) - self.assertEqual(res.storage["operation_map"][1]["approved_signers"], [alice]) - self.assertEqual(res.operations, []) - - def test_sign_operation_signer_should_execute_transaction(self): - """Enough signers sign the operation request, so it's executed""" - init_storage = deepcopy(initial_storage) - init_storage["operation_map"][1] = { - "target_to": bob, - "target_fa12": fa12, - "token_amount": TOKEN_AMOUNT, - "timestamp": 0, - "approved_signers": [alice, bob], - "executed": False - } - init_storage["operation_counter"] += 1 - expected_operation_request = { - "target_to": bob, - "target_fa12": fa12, - "token_amount": TOKEN_AMOUNT, - "timestamp": 0, - "approved_signers": [alice, bob, charly], - "executed": True - } - - res = self.multisig.sign(1).interpret(storage=init_storage, sender=charly) - - self.assertSetEqual(set(res.storage["operation_map"][1]["approved_signers"]), - set(expected_operation_request["approved_signers"])) - self.assertTrue(res.storage["operation_map"][1]["executed"]) - - self.assertEqual(res.operations[0]["kind"], "transaction") - self.assertEqual(res.operations[0]["destination"], fa12) - self.assertEqual(res.operations[0]["amount"], '0') - self.assertEqual(res.operations[0]["parameters"]["entrypoint"], "transfer") - # self.assertEqual(res.operations[0]["parameters"]["value"]["args"][0]["string"], self.multisig.address) - self.assertEqual(res.operations[0]["parameters"]["value"]["args"][1]["string"], bob) - self.assertEqual(res.operations[0]["parameters"]["value"]["args"][2]["int"], str(TOKEN_AMOUNT)) - - def test_unknown_tries_to_sign_should_fail(self): - """A random person, not one of the signers, tries to sign an operation request, so it doesn't work""" - init_storage = deepcopy(initial_storage) - init_storage["operation_map"][1] = { - "target_to": bob, - "target_fa12": fa12, - "token_amount": TOKEN_AMOUNT, - "timestamp": 0, - "approved_signers": [alice, bob], - "executed": False - } - init_storage["operation_counter"] += 1 - with self.raisesMichelsonError(only_signer): - self.multisig.sign(1).interpret(storage=init_storage, sender=unknown) - - def test_signer_tries_to_sign_unknown_request_should_fail(self): - """A signer tries to sign a request that doesn't exist, so it fails""" - init_storage = deepcopy(initial_storage) - with self.raisesMichelsonError(no_operation_exist): - self.multisig.sign(1).interpret(storage=init_storage, sender=bob) - - def test_unknown_tries_to_sign_unknown_request_should_fail(self): - """A random person tries to sign a request that doesn't exist, so it fails""" - init_storage = deepcopy(initial_storage) - with self.raisesMichelsonError(no_operation_exist): - self.multisig.sign(1).interpret(storage=init_storage, sender=bob) \ No newline at end of file