import "./messages"; struct Proposal { type: Int; // 0 - Blacklist, 1 - Full liquidation, 2 - Change percents blacklistAddress: Address?; distribution: Distribution?; } struct Vote { id: Int; votes: map[Int]Int; // -1 - no vote, 0 - abstain, 1 - yes, 2 - no vote_end: Int; proposal: Proposal; quorum_percent: Int; ended: Bool = false; result: Bool?; // None - no result, True - passed, False - failed } contract Foundation { founders: AddressList; // aka superadmins admins: Distribution/* Distribution is defined in messages.tact */; tonb: Address; votes: map[Int]Vote; profits: map[Address]Int; n_votes: Int = 0; init(admins: Distribution, founders: AddressList, tonb: Address) { self.admins = admins; self.founders = founders; self.tonb = tonb; } receive(msg: FinishVote) { require(msg.voteId < self.n_votes, "Invalid vote id"); let vote: Vote = self.votes.get(msg.voteId)!!; require(vote.ended == false, "Vote already ended"); require(vote.vote_end < now(), "Vote is not finished yet"); let n_yes: Int = 0; let n_no: Int = 0; let n_abstain: Int = 0; let i: Int = 0; while (i < self.admins.addresses.length) { let vote_i: Int = vote.votes.get(i)!!; if (vote_i == 1) { n_yes = n_yes + 1; } else if (vote_i == 2) { n_no = n_no + 1; } else if (vote_i == 0) { n_abstain = n_abstain + 1; } i = i + 1; } let n_total: Int = n_yes + n_no + n_abstain; let present: Int = n_total * 100 / self.admins.addresses.length; if (present < vote.quorum_percent) { vote.result = false; } else if (n_yes > n_no) { vote.result = true; } else { vote.result = false; } vote.ended = true; self.votes.set(msg.voteId, vote); if (vote.result == true) { let prop: Proposal = vote.proposal; if (prop.type == 0) { send(SendParameters{ to: self.tonb, value: 0, mode: SendRemainingValue, body: BlacklistWallet { wallet: prop.blacklistAddress!! }.toCell() }); } if (prop.type == 1) { // todo } if (prop.type == 2) { self.admins = prop.distribution!!; } } } receive(msg: VoteMsg) { require(msg.adminIndex < self.admins.addresses.length, "Invalid admin index"); let ctx: Context = context(); require(ctx.sender == self.admins.addresses.addresses.get(msg.adminIndex), "Only an admin can vote"); require(msg.voteId < self.n_votes, "Invalid vote id"); let vote: Vote = self.votes.get(msg.voteId)!!; require(vote.ended == false, "Vote already ended"); require(vote.vote_end > now(), "Vote is finished"); require(msg.vote >= 0 && msg.vote <= 2, "Invalid vote"); vote.votes.set(msg.adminIndex, msg.vote); self.votes.set(msg.voteId, vote); } receive(msg: InitiateBlacklistVote) { let ctx: Context = context(); require(ctx.sender == self.admins.addresses.addresses.get(msg.adminIndex), "Only an admin can initiate a vote"); require(ctx.value >= ton("1.0"), "Voting requires at least 1 ton for the fees"); require(msg.quorum_percent > 20 && msg.quorum_percent <= 100, "Invalid quorum percent"); require(msg.vote_time > 0 && msg.vote_time < 3 * 24 * 3600, "Invalid vote time"); let proposal: Proposal = Proposal { type: 0, blacklistAddress: msg.wallet, distribution: null }; let vote: Vote = Vote { id: self.n_votes, vote_end: now() + msg.vote_time, proposal: proposal, quorum_percent: msg.quorum_percent, votes: emptyMap(), result: null }; let i: Int = 0; while (i < self.admins.addresses.length) { vote.votes.set(i, -1); i = i + 1; } self.votes.set(self.n_votes, vote); self.n_votes = self.n_votes + 1; } // receive(msg: InitiateDistributionVote) { // let ctx: Context = context(); // require(ctx.sender == self.admins.addresses.addresses.get(msg.adminIndex), "Only an admin can initiate a vote"); // require(ctx.value >= ton("1.0"), "Voting requires at least 1 ton for the fees"); // require(msg.quorum_percent > 20 && msg.quorum_percent <= 100, "Invalid quorum percent"); // require(msg.vote_time > 0 && msg.vote_time < 3 * 24 * 3600, "Invalid vote time"); // let proposal: Proposal = Proposal { // type: 2, // blacklistAddress: null, // distribution: msg.distribution // }; // let vote: Vote = Vote { // id: self.n_votes, // vote_end: now() + msg.vote_time, // proposal: proposal, // quorum_percent: msg.quorum_percent, // votes: emptyMap(), // result: null // }; // let i: Int = 0; // while (i < self.admins.addresses.length) { // vote.votes.set(i, -1); // i = i + 1; // } // self.votes.set(self.n_votes, vote); // self.n_votes = self.n_votes + 1; // } receive(msg: InitiateLiquidationVote) { let ctx: Context = context(); require(ctx.sender == self.admins.addresses.addresses.get(msg.adminIndex), "Only an admin can initiate a vote"); require(ctx.value >= ton("1.0"), "Voting requires at least 1 ton for the fees"); require(msg.quorum_percent > 85 && msg.quorum_percent <= 100, "Invalid quorum percent"); require(msg.vote_time > 0 && msg.vote_time < 3 * 24 * 3600, "Invalid vote time"); let vote: Vote = Vote { id: self.n_votes, vote_end: now() + msg.vote_time, proposal: Proposal { type: 1, blacklistAddress: null, distribution: null }, quorum_percent: msg.quorum_percent, votes: emptyMap(), result: null }; let i: Int = 0; while (i < self.admins.addresses.length) { vote.votes.set(i, -1); i = i + 1; } self.votes.set(self.n_votes, vote); self.n_votes = self.n_votes + 1; } receive(msg: Unstake /* staking profits */) { // If this is sent by TONB, it means that this is receiving stake profits let ctx: Context = context(); if (ctx.sender == self.tonb) { let value: Int = ctx.value; let i: Int = 0; while (i < self.admins.addresses.length) { let addr: Address = self.admins.addresses.addresses.get(i)!!; let percent: Int = self.admins.percents.get(addr)!!; let amount: Int = value * percent / 100; // todo: record the profit let current_profit: Int = 0; let profit_record: Int? = self.profits.get(addr); if (profit_record != null) { current_profit = profit_record!!; } self.profits.set(addr, current_profit + amount); i = i + 1; } } } receive(msg: RequestUnstake) { // If this is sent by one of the founders, it means that the user wants to unstake let ctx: Context = context(); require(self.founders.addresses.get(msg.founderIndex) == ctx.sender, "Only a founder can request unstake"); send(SendParameters{ to: self.tonb, value: 0, mode: SendRemainingValue, body: Unstake { amount: 0 }.toCell() }); } receive(msg: CollectProfit) { let ctx: Context = context(); require(self.admins.addresses.addresses.get(msg.adminIndex) == ctx.sender, "Only an admin can collect profit"); let profit: Int? = self.profits.get(ctx.sender); require(profit != null, "No profit to collect"); let amount: Int = profit!!; self.profits.set(ctx.sender, 0); send(SendParameters{ to: ctx.sender, value: amount - withdraw_gas_consumption, mode: SendRemainingValue }); } }