|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|