import "./messages"; struct Proposal { type: Int; // 0 - Blacklist, 1 - Full liquidation, 2 - Change percents blacklistAddress: Address?; distribution_addresses: map[Int]Address; distribution_addresses_length: Int? = null; distribution_percents: map[Int]Int; } struct Vote { id: Int; votes: map[Int]Int; // -1 - no vote, 0 - abstain, 1 - yes, 2 - no vote_end: Int; 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; proposals: map[Int]Proposal; 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 + self.admins.percents.get(i)!!; } else if (vote_i == 2) { n_no = n_no + self.admins.percents.get(i)!!; } else if (vote_i == 0) { n_abstain = n_abstain + self.admins.percents.get(i)!!; } i = i + 1; } let n_total: Int = n_yes + n_no + n_abstain; let present: Int = n_total; 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 = self.proposals.get(msg.voteId)!!; 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 = Distribution{ addresses: AddressList{ addresses: prop.distribution_addresses, length: prop.distribution_addresses_length!! }, percents: prop.distribution_percents }; } } } 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_addresses: emptyMap(), distribution_addresses_length: null, distribution_percents: emptyMap() }; let vote: Vote = Vote { id: self.n_votes, vote_end: now() + msg.vote_time, 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.proposals.set(self.n_votes, proposal); 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_addresses: msg.distribution.addresses.addresses, distribution_addresses_length: msg.distribution.addresses.length, distribution_percents: msg.distribution.percents }; let vote: Vote = Vote { id: self.n_votes, vote_end: now() + msg.vote_time, 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.proposals.set(self.n_votes, proposal); 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, 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; } let proposal: Proposal = Proposal { type: 1, blacklistAddress: null, distribution_addresses: emptyMap(), distribution_addresses_length: null, distribution_percents: emptyMap() }; self.votes.set(self.n_votes, vote); self.proposals.set(self.n_votes, proposal); 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(i)!!; 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!!; require(amount > withdraw_gas_consumption, "No profit to collect"); self.profits.set(ctx.sender, 0); send(SendParameters{ to: ctx.sender, value: amount - withdraw_gas_consumption }); } receive() {} get fun numVotes(): Int { return self.n_votes; } get fun nthVote(n: Int): Vote? { return self.votes.get(n); } get fun AdminList(): AddressList { return self.admins.addresses; } get fun AdminPercents(): map[Int]Int { return self.admins.percents; } }