height 23745 of 23745 on the network. 9 peer(s) connected, 77 in its peer directory; 1041 block(s) followed and 865 pulled in by sync since this follower started. Caught up: this follower holds every block any peer has claimed, so every panel below is the whole chain. Genesis 1cf8655125cc1bbb…, producers genesis.
What this follower is, locally
Following 7 seed(s) — their addresses are not published here, for the reason the peer table gives. Its store is held under its own Cerberus lease — no other node’s store is opened, touched or stopped.
DaoTreasury
- Status
- template — not deployed A template is a draft, not a deployed contract. Nothing here is on the chain: it has no address until somebody deploys it, and where it lands depends on who deploys it and with which salt. Never send value to a template.
- File
templates/dao-treasury.ares- Profile
- native STR treasury
- Holds
- native STR
- The property that matters
- the electorate is frozen at proposal time, in both directions
- Compiler in force
areslang-1.3.0- Bytecode
- 5,807 bytes
- Bytecode SHA-256
d67bfdc5d9812d8cd4a7843279a014d6519fd5c94adf608d4434d3692d86b24a- Source SHA-256
ccef55fc383098fd9ebb7815a2d8792f247f8e2c06ee176413a3862d95393e24- Published vector
- matches the published vector spec/conformance-vectors.json pins d67bfdc5d9812d8c… under areslang-1.3.0
- Catalogue
- every template
Before you deploy
The header marks these for editing before you deploy. The values below are the examples it ships with; deployed unedited, they are what every holder sees for the life of the contract.
| Constant | Type | Shipped as |
|---|---|---|
votingPeriodMs | uint | 604800000 |
minRollSize | uint | 3 |
Where your deployment would land
Name a deployer to see where its deployment of this template, unedited, would land.
The chain derives a contract’s address from its name, its bytecode, the deployer and the salt. This is that function over the bytecode above, so it holds only for the template exactly as shown and only while the chain runs the same compiler. Edit one constant and the address moves, which is correct: different code is a different contract. Nothing is at this address until somebody deploys there.
Measured gas
What the STARW VM charged for each call on a real chain, under areslang-1.3.0, and asserted exactly by the catalogue test: a figure that moved would fail the build. Measured, not estimated. A call that stores a string costs more for a longer one; those were measured with ipfs://QmExampleTokenMetadataDocument01.
| Entry point | Gas |
|---|---|
| execute | 3,338 |
| proposeRemove | 2,838 |
| proposeAdmit | 2,786 |
| propose | 2,725 |
| execute(admit) | 2,051 |
| execute(remove) | 1,994 |
| admit | 1,509 |
| remove | 1,386 |
| vote | 1,199 |
| renounceSteward | 673 |
- Declare at least
- 33,380 ten times the most expensive call; post-mining covers only a tenth of the declared limit
ABI
| Kind | Name | Inputs | Outputs | Mutability |
|---|---|---|---|---|
| function | admit | who: address | uint | nonpayable |
| function | remove | who: address | uint | nonpayable |
| function | renounceSteward | uint | nonpayable | |
| function | propose | to: address, amount: uint | uint | nonpayable |
| function | proposeAdmit | who: address | uint | nonpayable |
| function | proposeRemove | who: address | uint | nonpayable |
| function | vote | id: uint | uint | nonpayable |
| function | execute | id: uint | uint | nonpayable |
| function | stewardIs | address | view | |
| function | stewardHasPower | bool | view | |
| function | members | uint | view | |
| function | minimumRoll | uint | view | |
| function | currentEpoch | uint | view | |
| function | isMember | who: address | bool | view |
| function | held | uint | view | |
| function | proposals | uint | view | |
| function | kindOf | id: uint | uint | view |
| function | recipientOf | id: uint | address | view |
| function | amountOf | id: uint | uint | view |
| function | rollOf | id: uint | uint | view |
| function | epochOf | id: uint | uint | view |
| function | yesOf | id: uint | uint | view |
| function | closingOf | id: uint | uint | view |
| function | wasExecuted | id: uint | bool | view |
| function | hasVoted | id: uint, who: address | bool | view |
| event | Admitted | who: address, epoch: uint, members: uint | ||
| event | Removed | who: address, epoch: uint, members: uint | ||
| event | StewardRenounced | previous: address, members: uint | ||
| event | Proposed | id: uint, kind: uint, by: address, to: address, amount: uint, roll: uint, closesAt: uint | ||
| event | Voted | id: uint, by: address, yes: uint, roll: uint | ||
| event | Executed | id: uint, to: address, amount: uint |
From the command line
Print the source to a file, edit the marked constants, dry-run it to see the digest and your address, then deploy. Only the last command signs, and it is the only one that needs your key and a node.
strblk contract template dao-treasury > dao-treasury.ares strblk contract template dao-treasury --info strblk wallet deploy dao-treasury.ares --dry-run --address <your address> strblk wallet deploy dao-treasury.ares --node <your node's RPC>
Source
templates/dao-treasury.ares, as embedded in this build
// =========================================================================================
// DaoTreasury — a member roll votes on payments of native STR out of this contract, and on
// who is on the roll.
//
// WHAT IT GOVERNS, AND THIS IS THE PART PEOPLE GET WRONG
// This contract's own balance. Nothing else. It is NOT SourceLess governance: chain-wide
// parameters belong to the Althing, at a strict majority of 66,360 declared power, and the
// only governance actions that exist there are set-parameter over a bounded registry,
// admit-member and remove-member — deliberately, because a governance system that can
// change anything is a governance system that can be used to steal. No contract can touch
// any of it. A DAO template that presents itself as SourceLess governance is presenting
// authority it does not have, and this one does not.
//
// WHAT IT GUARANTEES
// * The electorate is FROZEN at proposal time. A proposal records the epoch it was created
// in and the member count at that instant. Admitting members afterwards does not give
// them a vote on it, and removing members afterwards does not shrink the majority it
// needs. Both directions are bugs in live-membership tallying: admitting a large member
// mid-vote defeats a proposal that had already passed, and removing members shrinks the
// threshold until an existing minority's votes clear it.
// * One member, one vote, per proposal. A second vote reverts.
// * A strict majority of the frozen roll is required: yes * 2 > roll. A tie does not pass.
// * A proposal executes at most once, only after it passes, and only before it expires.
// Anyone may trigger that execution — it can only do what the frozen roll approved, to the
// address the proposal named — so a passed payment cannot stall behind a quiet member.
// * There are exactly three kinds of proposal — pay, admit, remove — and no fourth. They are
// deliberately the Althing's three: govern the thing you govern, admit a member, remove a
// member. Nothing here writes arbitrary state, because a governance system that can change
// anything is a governance system that can be used to steal.
// * ONCE THE ROLL IS SEALED, ONLY THE ROLL CHANGES THE ROLL. `proposeAdmit` and
// `proposeRemove` need the same strict majority a payment needs, and there is no key
// anywhere — the retired steward's included — that can put an address on the roll or take
// one off it alone.
// * A roll of at least `minRollSize`, which ships at three. One member is a majority of one.
// * The steward cannot spend, and here is why that is a fact rather than a claim about the
// ABI. `execute` can only pay a proposal; `propose` is the only thing that creates one;
// and `propose` refuses to run while `stewardActive` is true. So while the steward has any
// power over the roll, NO VALUE CAN LEAVE THIS CONTRACT BY ANY PATH — and after
// `renounceSteward()`, which is one-way, the steward has no power over the roll at all.
// The two states are exclusive by construction, which is the only way this promise can be
// kept: see below for what was true before it was.
//
// THE POWER THE STEWARD KEEPS, DECLARED, AND WHY IT ENDS BEFORE THE MONEY MOVES
// The deployer is the steward and can `admit` and `remove` members while the roll is open.
// That is real power — it decides who will vote on everything this treasury ever does — and
// it is in the ABI as `stewardIs()`, `stewardHasPower()`, `admit` and `remove` so that a
// member can see it before joining and a funder can see it before paying in.
//
// IT IS ALSO, IF IT OUTLIVED THE FUNDING, A CLAIM ON THE WHOLE BALANCE. A steward that can
// admit members can admit members IT CONTROLS, and nothing in this contract — nothing in
// this chain, from inside a contract — can tell an independent member from an address the
// steward holds the key to. So a live steward is, in the limit, a majority of any future
// roll. An earlier version of this template said "the steward cannot spend" on the grounds
// that no function moving value checks `steward`, and an audit executed the counterexample
// on a real chain: remove all five members, admit yourself, propose a payment to yourself,
// vote once against a roll of one, execute. Whole treasury, four calls, chain still valid.
// The self-admit ban and the roll floor below make that particular sequence revert; they do
// not, and cannot, stop a steward that simply admits four addresses it owns.
//
// The template therefore separates the two powers in time instead of trying to bound one of
// them, and hands the roll to the roll. Build the roll, then `renounceSteward()`, then fund
// it. After renunciation `stewardHasPower()` reads false, the steward has no function it can
// call, and the membership at that instant is readable in full from the `Admitted` and
// `Removed` log — a set decided before any money was at stake, that a funder can inspect
// before paying in. DO NOT FUND A TREASURY WHOSE `stewardHasPower()` IS STILL TRUE. It
// cannot spend, but it can still decide, alone, who will.
//
// AFTER THAT, THE ROLL IS NOT FROZEN — IT IS SELF-GOVERNING. A sealed roll would have made
// a lost key permanent: every proposal needs a strict majority of the whole roll, so a dead
// seat is a vote against everything, and a roll of five with three dead keys could never pay
// anything again. So the two membership actions the steward loses become proposals the
// members vote on, at the same strict majority, with the same frozen electorate. What that
// costs is stated plainly: a majority of the roll can admit anybody, including addresses one
// member controls. A DAO's members can always vote to do something foolish; the property
// this template keeps is that NO SINGLE KEY CAN. What it cannot survive is losing a majority
// of its keys at once — at that point nothing passes, including the removals that would fix
// it, and the balance is unreachable forever, because the code is immutable and there is no
// administrator anywhere in SourceLess who can reach into a contract's slots. Size the roll
// for that, and read `members()` as the denominator it is.
//
// THE IDENTITY RULE — CORRECTED 2026-09-08
// This block described a rule that no longer exists. `ValueJournal.receiveRefusal` used to
// require a payment recipient to hold an ALLOCATED STR.domain or be a deployed contract; it
// now refuses only Hnitbjorg reserved accounts. The gate became MORE permissive, so nothing
// built against the old text is unsafe — but it instructed issuers to pre-flight a check that
// no longer applies, and told them a payout would revert when it will not.
//
// Today: any well-formed, non-reserved address, or a deployed contract, may be paid. What a
// recipient with no proved domain still cannot do is SPEND what it receives — a fact about
// that holder, not about this contract.
//
// HOW IT IS FUNDED
// By plain STR transfers to its address; a call record carries no value. The contract
// cannot see who funded it, so contributing confers no claim and no vote. Membership is the
// only thing that confers a vote, which is deliberate: a treasury that gave votes for
// money would be computing voting weight over an unauthenticated set.
//
// HOW IT IS TYPICALLY MISUSED
// * Assuming a proposal that has passed will still be executable later. It expires. That is
// on purpose: an approved payment sitting unexecuted for a year was approved by a roll
// that may no longer exist.
// * Admitting an address that is a contract. A contract cannot call anything, so a contract
// member can never vote — the seat is dead weight that still counts toward the majority
// every proposal needs. The same is true of any key that is lost after renunciation, and
// there is no way to retire either.
// * Funding before renouncing, then wondering why `propose` reverts with "the steward still
// holds the roll". The order is: admit, renounce, fund, propose. Money sent earlier is not
// lost — nothing can move it until the roll is final, which is the point — but it sits in
// a contract whose electorate one key is still choosing.
// * Voting on a membership proposal without reading `kindOf(id)`. An admission and a removal
// both carry `amountOf(id) == 0`, so a member who only checks the amount sees a proposal
// with nothing at stake and votes it through. The address is in `recipientOf(id)` and the
// kind is in `kindOf(id)`; a wallet that shows a DAO proposal must show both.
// * Under-declaring the gas limit. MEASURED: admit 1,509, remove 1,386, renounceSteward 673,
// propose 2,725, proposeAdmit 2,786, proposeRemove 2,838, vote 1,199, execute 3,338 on a
// payment, 2,051 on an admission and 1,994 on a removal. Declare at least 34,000.
// =========================================================================================
contract DaoTreasury {
// ---- edit before deploying -----------------------------------------------------------
// How long a proposal stays open, in milliseconds. 7 days.
state votingPeriodMs: uint = 604800000;
// The smallest roll this treasury will govern with. A roll of one is a majority of one, and
// a roll of two is a majority of two — neither is a vote, and both are a single key holding
// the whole balance while looking like a DAO in the ABI. The floor is enforced where it
// matters: `renounceSteward` refuses to seal a roll below it, `remove` refuses to take the
// roll below it, `execute` refuses a voted removal that would breach it, and the three
// propose functions refuse to put a question to a roll below it. Raise it; do not lower it
// below 3.
state minRollSize: uint = 3;
// -------------------------------------------------------------------------------------
state steward: address = caller;
state stewardActive: bool = true;
state memberCount: uint = 0;
// Bumped on every membership change. A proposal remembers the epoch it was created in, and
// eligibility is answered against THAT epoch rather than against the roll as it is now.
state epoch: uint = 1;
state proposalCount: uint = 0;
// The epoch a member was admitted in. 0 means never admitted.
state joinedAt: map<address, uint>;
// The epoch a member was removed in. 0 means still a member.
state leftAt: map<address, uint>;
// One proposal is a row across these maps. AresLang has no structs.
// 1 = pay, 2 = admit, 3 = remove. Those are the only three things this treasury can decide,
// and they are deliberately the same three the Althing has: set the parameters of the thing
// you govern, admit a member, remove a member. A proposal kind that could write arbitrary
// state is a governance system that can be used to steal.
state proposalKind: map<uint, uint>;
// The address the proposal names: the recipient of a payment, or the member an admission or
// a removal is about.
state payTo: map<uint, address>;
// Zero on an admission and on a removal.
state payAmount: map<uint, uint>;
state rollAt: map<uint, uint>;
state rollSize: map<uint, uint>;
state yesVotes: map<uint, uint>;
state closesAt: map<uint, uint>;
state executed: map<uint, bool>;
state voted: map<uint, map<address, bool>>;
event Admitted(who: address, epoch: uint, members: uint);
event Removed(who: address, epoch: uint, members: uint);
event StewardRenounced(previous: address, members: uint);
event Proposed(id: uint, kind: uint, by: address, to: address, amount: uint, roll: uint, closesAt: uint);
event Voted(id: uint, by: address, yes: uint, roll: uint);
event Executed(id: uint, to: address, amount: uint);
// ---- the roll ---------------------------------------------------------------------------
fn admit(who: address) -> uint {
require(stewardActive == true, "the roll is closed");
require(caller == steward, "not the steward");
// The steward names the roll, so the steward must not be on it. Without this line the
// shortest attack on this contract is four calls: remove every member, admit yourself,
// propose a payment to yourself, vote once — one yes against a roll of one is a strict
// majority — and execute. MEASURED: the whole treasury, and the chain stayed valid.
require(who != steward, "the steward cannot sit on the roll");
// And this contract cannot sit on its own roll. A contract can never be `caller`, so
// such a member is a seat that can never vote: it counts towards the roll a majority is
// measured against and can never be part of one, and after `renounceSteward` the only
// way to remove it is a vote by the roll it is handicapping.
require(who != self, "self cannot sit on the roll");
require(joinedAt[who] == 0 || leftAt[who] > 0, "already a member");
epoch = epoch + 1;
joinedAt[who] = epoch;
// Cleared, so a re-admitted member is a member again from this epoch onwards and is
// still correctly excluded from proposals created while they were out.
leftAt[who] = 0;
memberCount = memberCount + 1;
emit Admitted(who, epoch, memberCount);
return memberCount;
}
fn remove(who: address) -> uint {
require(stewardActive == true, "the roll is closed");
require(caller == steward, "not the steward");
// The floor, on the way down. Shrinking a roll is the other half of packing it: a steward
// that can remove members can remove everyone who disagrees until the remainder is a
// majority. While the steward is active nothing can be proposed at all (see `propose`), so
// this bound is not load-bearing on its own — it is here so that it stays true if that
// ever changes. The cost is that a member admitted by mistake cannot be removed until the
// roll is ABOVE the floor: admit the rest of the roll first, then remove.
require(memberCount > minRollSize, "the roll cannot fall below its floor");
require(joinedAt[who] > 0 && leftAt[who] == 0, "not a member");
epoch = epoch + 1;
leftAt[who] = epoch;
memberCount = memberCount - 1;
emit Removed(who, epoch, memberCount);
return memberCount;
}
// One-way, and the hinge of the whole template. Before it, the roll is one key's to build
// and nothing can be proposed; after it, the treasury can pay and the roll belongs to its own
// members, who change it with `proposeAdmit` and `proposeRemove` at the same strict majority
// a payment needs. The steward keeps nothing: `admit`, `remove` and this function all refuse
// a closed roll, and it was never a member, so it has no vote either.
fn renounceSteward() -> uint {
require(stewardActive == true, "the roll is closed");
require(caller == steward, "not the steward");
// This is the moment the roll becomes final and the treasury becomes spendable, so it is
// the moment the floor has to hold. An empty roll would freeze the treasury forever; a
// roll of one or two would hand it to one or two keys.
require(memberCount >= minRollSize, "the roll is below its floor");
stewardActive = false;
emit StewardRenounced(steward, memberCount);
return memberCount;
}
// ---- proposals ---------------------------------------------------------------------------
fn propose(to: address, amount: uint) -> uint {
// NOTHING CAN BE PROPOSED WHILE THE STEWARD CAN STILL CHANGE THE ROLL, and this one line
// is what makes "the steward cannot spend" a fact rather than a hope. A steward that can
// admit members can admit members it controls, and no contract can tell an independent
// member from an address the steward holds the key to — so any guard short of this one
// only raises the number of calls a drain takes. `execute` can only pay a proposal, and
// `propose` is the only thing that creates one, so while `stewardActive` is true no value
// can leave this contract by any path. `renounceSteward` is one-way, so afterwards the
// steward has no power here at all.
require(stewardActive == false, "the steward still holds the roll");
// A payment to this contract can win its majority and then revert on every execution,
// because the value ledger refuses a contract that pays itself. Refused where it costs
// one call, rather than after a whole roll has voted on it.
require(to != self, "self is not payable");
require(joinedAt[caller] > 0 && leftAt[caller] == 0, "not a member");
// The floor, tested where the question is put. `renounceSteward` refuses to seal a roll
// below it and `execute` refuses a removal that would breach it, so this cannot fail
// today — it is the check that keeps the majority test in `execute` meaningful if either
// of those is ever edited.
require(memberCount >= minRollSize, "the roll is below its floor");
require(amount > 0, "amount must be positive");
// ---- PROPOSAL BEGIN (copy 1 of 3) ----
// The three propose functions differ only in the kind they record and the arguments they
// check. Everything below is copied verbatim between the PROPOSAL BEGIN and PROPOSAL END
// markers because AresLang has no user-defined functions. IF YOU CHANGE ONE, CHANGE ALL
// THREE: a proposal whose roll is frozen on one path and not on another is the whole
// vulnerability this template exists to avoid. A test extracts all three from this file
// and fails if they are not character-for-character the same apart from the copy number.
let id = proposalCount + 1;
proposalCount = id;
// The freeze. Both halves matter: the epoch decides WHO may vote, and the size decides
// HOW MANY yes votes it takes. Neither can move afterwards.
rollAt[id] = epoch;
rollSize[id] = memberCount;
yesVotes[id] = 0;
closesAt[id] = timestamp + votingPeriodMs;
executed[id] = false;
// ---- PROPOSAL END ----
proposalKind[id] = 1;
payTo[id] = to;
payAmount[id] = amount;
emit Proposed(id, 1, caller, to, amount, memberCount, closesAt[id]);
return id;
}
// The roll's own two membership actions, which is how the roll changes at all once the
// steward is gone. Both go through the same vote a payment does, so no single key — the
// retired steward's included — can put an address on the roll or take one off it. The
// members may vote to admit the former steward, or to pay it; that is their decision and not
// its power, which is the distinction this whole template turns on.
fn proposeAdmit(who: address) -> uint {
require(stewardActive == false, "the steward still holds the roll");
// The same seat-that-cannot-vote, by the other door. `admit` refuses it too.
require(who != self, "self cannot sit on the roll");
require(joinedAt[caller] > 0 && leftAt[caller] == 0, "not a member");
require(memberCount >= minRollSize, "the roll is below its floor");
require(joinedAt[who] == 0 || leftAt[who] > 0, "already a member");
// ---- PROPOSAL BEGIN (copy 2 of 3) ----
// The three propose functions differ only in the kind they record and the arguments they
// check. Everything below is copied verbatim between the PROPOSAL BEGIN and PROPOSAL END
// markers because AresLang has no user-defined functions. IF YOU CHANGE ONE, CHANGE ALL
// THREE: a proposal whose roll is frozen on one path and not on another is the whole
// vulnerability this template exists to avoid. A test extracts all three from this file
// and fails if they are not character-for-character the same apart from the copy number.
let id = proposalCount + 1;
proposalCount = id;
// The freeze. Both halves matter: the epoch decides WHO may vote, and the size decides
// HOW MANY yes votes it takes. Neither can move afterwards.
rollAt[id] = epoch;
rollSize[id] = memberCount;
yesVotes[id] = 0;
closesAt[id] = timestamp + votingPeriodMs;
executed[id] = false;
// ---- PROPOSAL END ----
proposalKind[id] = 2;
payTo[id] = who;
payAmount[id] = 0;
emit Proposed(id, 2, caller, who, 0, memberCount, closesAt[id]);
return id;
}
fn proposeRemove(who: address) -> uint {
require(stewardActive == false, "the steward still holds the roll");
require(joinedAt[caller] > 0 && leftAt[caller] == 0, "not a member");
require(memberCount >= minRollSize, "the roll is below its floor");
require(joinedAt[who] > 0 && leftAt[who] == 0, "not a member");
// ---- PROPOSAL BEGIN (copy 3 of 3) ----
// The three propose functions differ only in the kind they record and the arguments they
// check. Everything below is copied verbatim between the PROPOSAL BEGIN and PROPOSAL END
// markers because AresLang has no user-defined functions. IF YOU CHANGE ONE, CHANGE ALL
// THREE: a proposal whose roll is frozen on one path and not on another is the whole
// vulnerability this template exists to avoid. A test extracts all three from this file
// and fails if they are not character-for-character the same apart from the copy number.
let id = proposalCount + 1;
proposalCount = id;
// The freeze. Both halves matter: the epoch decides WHO may vote, and the size decides
// HOW MANY yes votes it takes. Neither can move afterwards.
rollAt[id] = epoch;
rollSize[id] = memberCount;
yesVotes[id] = 0;
closesAt[id] = timestamp + votingPeriodMs;
executed[id] = false;
// ---- PROPOSAL END ----
proposalKind[id] = 3;
payTo[id] = who;
payAmount[id] = 0;
emit Proposed(id, 3, caller, who, 0, memberCount, closesAt[id]);
return id;
}
// A yes vote. There is no no-vote to record: a proposal needs a strict majority of the
// whole frozen roll, so not voting IS voting against.
fn vote(id: uint) -> uint {
require(id > 0 && id <= proposalCount, "no such proposal");
require(executed[id] == false, "already executed");
require(timestamp < closesAt[id], "voting has closed");
// Eligibility against the FROZEN epoch, never against the roll as it is now. A member
// admitted after this proposal was created has joinedAt > rollAt and cannot vote on it;
// a member removed after it was created has leftAt > rollAt and still can.
require(joinedAt[caller] > 0, "not a member");
require(joinedAt[caller] <= rollAt[id], "admitted after this proposal");
require(leftAt[caller] == 0 || leftAt[caller] > rollAt[id], "removed before this proposal");
require(voted[id][caller] == false, "already voted");
voted[id][caller] = true;
let count = yesVotes[id] + 1;
yesVotes[id] = count;
emit Voted(id, caller, count, rollSize[id]);
return count;
}
// ANYONE may trigger execution, deliberately: it can only ever do what the frozen roll
// already approved, to the address the proposal already named, once. Gating it on
// membership would add no authority and would let a payment stall because the members who
// voted for it had all gone quiet. (The multisig template makes the opposite choice, and
// says so; there the owner set is the whole of the contract's identity.)
//
// Returns the amount paid for a payment, and the resulting member count for an admission or
// a removal.
fn execute(id: uint) -> uint {
require(id > 0 && id <= proposalCount, "no such proposal");
require(executed[id] == false, "already executed");
require(timestamp < closesAt[id], "proposal expired");
// A strict majority of the roll as it was when the proposal was made. Not of the roll as
// it is now, and not of the members who happened to turn up.
require(yesVotes[id] * 2 > rollSize[id], "no majority");
// Set before any effect and before the transfer. A revert unwinds it with everything else,
// so a proposal the ledger refused to pay is still executable; a proposal that paid is
// never executable again.
executed[id] = true;
let who = payTo[id];
if (proposalKind[id] == 1) {
let amount = payAmount[id];
require(balance(self) >= amount, "insufficient balance");
transfer(who, amount);
emit Executed(id, who, amount);
return amount;
}
if (proposalKind[id] == 2) {
// Re-checked HERE, against the roll as it is now, and not only at proposal time: two
// admissions of the same address could both pass, and the second must not double-count
// the member. The same for a removal of somebody already gone.
require(joinedAt[who] == 0 || leftAt[who] > 0, "already a member");
epoch = epoch + 1;
joinedAt[who] = epoch;
leftAt[who] = 0;
memberCount = memberCount + 1;
emit Admitted(who, epoch, memberCount);
return memberCount;
}
// Kind 3, by elimination and not by accident: `id <= proposalCount` means the row was
// written by one of the three propose functions, and each of them writes its kind before
// it returns, so there is no fourth value this can be.
require(joinedAt[who] > 0 && leftAt[who] == 0, "not a member");
// The floor holds on this path too. A roll that can vote itself down to one has voted
// itself into a single key holding the balance.
require(memberCount > minRollSize, "the roll cannot fall below its floor");
epoch = epoch + 1;
leftAt[who] = epoch;
memberCount = memberCount - 1;
emit Removed(who, epoch, memberCount);
return memberCount;
}
// ---- reads ------------------------------------------------------------------------------
view fn stewardIs() -> address {
return steward;
}
view fn stewardHasPower() -> bool {
return stewardActive;
}
view fn members() -> uint {
return memberCount;
}
// The floor this treasury refuses to govern below. Readable before anyone funds it.
view fn minimumRoll() -> uint {
return minRollSize;
}
view fn currentEpoch() -> uint {
return epoch;
}
view fn isMember(who: address) -> bool {
return joinedAt[who] > 0 && leftAt[who] == 0;
}
view fn held() -> uint {
return balance(self);
}
view fn proposals() -> uint {
return proposalCount;
}
// 1 = pay, 2 = admit, 3 = remove. Read it before you vote: the amount is zero on the two
// membership kinds, so an eye that only checks `amountOf` sees nothing at stake.
view fn kindOf(id: uint) -> uint {
return proposalKind[id];
}
// The recipient of a payment, or the member an admission or a removal is about.
view fn recipientOf(id: uint) -> address {
return payTo[id];
}
view fn amountOf(id: uint) -> uint {
return payAmount[id];
}
view fn rollOf(id: uint) -> uint {
return rollSize[id];
}
view fn epochOf(id: uint) -> uint {
return rollAt[id];
}
view fn yesOf(id: uint) -> uint {
return yesVotes[id];
}
view fn closingOf(id: uint) -> uint {
return closesAt[id];
}
view fn wasExecuted(id: uint) -> bool {
return executed[id];
}
view fn hasVoted(id: uint, who: address) -> bool {
return voted[id][who];
}
}