height 23718 of 23718 on the network. 9 peer(s) connected, 77 in its peer directory; 1014 block(s) followed and 843 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 for your fork
- Status
- a draft, not deployed nothing here is on any chain; never send value to a draft
- Profile
- native STR treasury
- Holds
- native STR
- The property that matters
- the electorate is frozen at proposal time, in both directions
- On Asgard
- the same template in the catalogue
Edit the draft
Set the constants the template marks for editing, and name a deployer to see where it would land.
The draft as shipped
- Contract
DaoTreasury- Compiler
areslang-1.3.0- Bytecode SHA-256
d67bfdc5d9812d8cd4a7843279a014d6519fd5c94adf608d4434d3692d86b24a- Bytecode
- 5,807 bytes
- Source SHA-256
ccef55fc383098fd9ebb7815a2d8792f247f8e2c06ee176413a3862d95393e24- The catalogue text?
- Yes, byte for byte: matches the published vector
- Ready to deploy?
- no catalogue examples left
- Would land at
- name a deployer to see it
Deploy it to your fork
Only the last step signs, and only it reaches a node.
- Save the draft. Download it above, or start from the catalogue text with
strblk contract template dao-treasury > dao-treasury.aresand edit the marked constants yourself. - Dry-run it on your machine.
strblk wallet deploy dao-treasury.ares --dry-run --address <your address on the fork>compiles it locally and prints its digest and address. They must match this page; if they do not, the file is not this draft. - Find your fork's RPC. It is the
rpc (Iris, wallets)line of the fork's banner, and therpcUrlof each node in<data root>/fork.json. - Deploy it.
strblk wallet deploy dao-treasury.ares --node <your fork's RPC>, from a wallet whose account holds a proved STR.domain on your fork. Before it signs, the wallet prints the network: the realm reads unregistered and the genesis is your fork's hash. Check that hash; it is what the signature binds.
Measured gas
Charged by the STARW VM under areslang-1.3.0, the same on your fork. Measured, not estimated.
| 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
Functions
| 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 |
Your draft's source
templates/dao-treasury.ares, as shipped
// =========================================================================================
// 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];
}
}