A Nouns token delegation vulnerability
A Nouns token delegation vulnerabilityWe were recently approached by 0xkasper from hexens, a white hat security team, who found a new attack vector based on a vulnerability we were aware of in the Nouns token contract. Nouns tech grants pod has initiated a bounty payment of 30K USDC to the team.TLDRWho’s at risk? Noun owners delegating to EOA accounts (not smart contract accounts).What’s the damage? Noun NFTs can become non-transferrable with no voting power.How likely is it to happen? The pr...
Introducing Nouns Fork: A Last-Resort Minority Protection Mechanism
Forking is the crypto-native way for groups of token holders to exit together into a new instance of their protocol, resulting in maximal conservation of momentum in the ecosystem; famous examples include the Ethereum Classic fork and the Bitcoin Cash fork. We believe it's time to enable DAO forks as well, starting here at Nouns. To date, there has been no DAO version of forking, but it is needed. How can we protect a minority against a tyrannical majority? Or, in less extreme cases, how...
Nouns Governor: Design Alternatives
Welcome to the second post in the Nouns Governor series; Please check out the first post for more context. This time we’d like to share three different design directions we have in mind, with some high level details on how each direction might work. Our hope is the same as last time: to receive feedback from the community; do you have a preferred design direction? Do you think none of them are good enough? The current focus is on how delegation information is encoded and presented. The curren...
we build smart contracts for Nouns. we're on twitter @davidbrai and @eladmallel.
A Nouns token delegation vulnerability
A Nouns token delegation vulnerabilityWe were recently approached by 0xkasper from hexens, a white hat security team, who found a new attack vector based on a vulnerability we were aware of in the Nouns token contract. Nouns tech grants pod has initiated a bounty payment of 30K USDC to the team.TLDRWho’s at risk? Noun owners delegating to EOA accounts (not smart contract accounts).What’s the damage? Noun NFTs can become non-transferrable with no voting power.How likely is it to happen? The pr...
Introducing Nouns Fork: A Last-Resort Minority Protection Mechanism
Forking is the crypto-native way for groups of token holders to exit together into a new instance of their protocol, resulting in maximal conservation of momentum in the ecosystem; famous examples include the Ethereum Classic fork and the Bitcoin Cash fork. We believe it's time to enable DAO forks as well, starting here at Nouns. To date, there has been no DAO version of forking, but it is needed. How can we protect a minority against a tyrannical majority? Or, in less extreme cases, how...
Nouns Governor: Design Alternatives
Welcome to the second post in the Nouns Governor series; Please check out the first post for more context. This time we’d like to share three different design directions we have in mind, with some high level details on how each direction might work. Our hope is the same as last time: to receive feedback from the community; do you have a preferred design direction? Do you think none of them are good enough? The current focus is on how delegation information is encoded and presented. The curren...
we build smart contracts for Nouns. we're on twitter @davidbrai and @eladmallel.

Subscribe to verbs

Subscribe to verbs
Share Dialog
Share Dialog
>400 subscribers
>400 subscribers
Like any other protocol, Nouns DAO relies on clients (webapps / mobile) to interact with its smart contracts. There are many reasons for the DAO to benefit from having multiple clients available, to name a few:
Redundancy of clients reduces risk of technical issues limiting access
The ability to sunset or stop developing nouns.wtf which is currently being maintained by Nounders
Gives space for new clients to emerge, including specialized clients like a proposal builder
Currently nouns.wtf is in an awkward place where it’s still probably the most popular client, but at the same time it’s not being actively maintained. It’s also not entirely clear how decisions are made on what can be added to the site.
We want to propose a model where clients get paid by the DAO based on usage. By usage we mean interactions with the smart contracts such as bidding on auctions, creating proposals, and voting. This is similar to the way Liquity incentivizes frontends with “kickbacks”.
We hope this will create the following incentives:
Incentivize clients to provide great UX (and do some minimum marketing) to drive more usage to their apps.
Incentivizes clients to solve for all the interactions the DAO pays for (and we can pay more for interactions with insufficient coverage).
Incentivizes clients to keep up with DAO upgrades, so they remain functional and continue to be used.
The simplest way this can work is by adding a client parameter to the function calls, e.g.:
castVote(proposalId, support, reason, client) where client is the wallet address of the client that facilitated this transaction.
Let’s discuss the key interactions we think are worth paying for, how they could be paid, and how to make sure they can’t be gamed.
We don’t want to pay for every createBid call because that can be easily gamed by making many low value bids. One way to get around it is to only pay for the interaction of the winning bid.
Fixed percentage of the auction settlement amount.
Fixed ETH amount.
Other options you think make sense? Please let us know
Having great proposal creation tools is important, supporting good previews, different transaction types, etc.
We don’t want to pay for every proposal creation because it may incentivize people to create spam proposals just to get the payment.
A few options on how to avoid it:
Only pay for successful proposals that get executed.
Only pay for proposals that pass quorum.
Let us know if you have more ideas.
Fixed ETH amount per proposal.
Fixed percentage of the amount of funding asked in the proposal. This is problematic because it creates weird incentives, and not all proposals are directly asking for funding.
Allocate a percentage of the DAO’s income to pay for proposals created, e.g. X% of auction revenue per month is used to pay governance clients for new valid proposals (valid meaning passed anti-gaming filters mentioned above).
Pay out disproportionately to top clients (e.g. 1st place gets 2x more than 2nd place).
In order to prevent spam proposals created just for voting payments, we can use the same method as mentioned above, i.e. only count votes made on “valid” proposals.
Fixed ETH amount per vote transaction.
Fixed ETH amount per vote, i.e. if voting with 2 nouns, client gets paid double.
Fixed ETH amount per proposal: e.g. if 1 ETH is paid out per proposal, and a proposal had 100 votes, then for each vote, the originating client gets 0.01 ETH.
We think the interactions listed above are the key interactions the DAO should pay for and those should be in the scope of the first iteration of this model.
Other interactions and possible payment options:
Proposal updates (updateProposal)
We don’t want to incentivize updating proposals for payouts because that’s easily gamed by doing no-op updates. Perhaps it makes sense to pay the client which originated the most recent version of a proposal. If a proposal was updated, we would update the originating client for that proposal. It makes sense for clients to support both functionalities.
Proposal candidates
All the interactions related to candidates have the goal of eventually turning the candidate into a Proposal via proposeBySigs.
Payouts for proposal creation provide incentive for clients to support all the functionality of proposal candidates.
Assuming this is enough incentive, perhaps the specific candidate interactions (creating, updating, signing, feedback) don’t need payouts.
Delegation
Don’t have a good idea on how to prevent spam delegations for payouts. Open to suggestions!
Forking
Probably makes sense for fork-related interactions to be paid for by both OG DAO and the fork DAO.
Probably makes sense to pay per forking Noun, whether put in escrow or joining after fork DAO creation; this allows multiple clients to contribute to a single fork event and be fairly compensated.
How much should be paid per forking Noun? Should it be a nominal amount, e.g. 0.01 ETH? Or should it be a percentage of BV? Any other ideas?
A more general way to try to game the system, is to pretend to be a client by putting your own wallet address when interacting with the DAO contracts. This gets worse if people actually use clients, but swap the client address with their own when posting transactions.
One way to mitigate this risk is for the DAO to maintain a list of approved clients that can be paid. Whenever a new client wants to be added to this list, they can put up a proposal. It also serves as a way to advertise that this client exists.
Alternatively there could be some algorithmic way to determine that a client is likely not fake, e.g. condition payout on a minimum number of interactions, from different wallets, etc.
We’d like your feedback here. Do you think it’s better to keep a list of approved clients or rather take the permissionless route of codifying some rules to identify “real” clients? If the latter, what rules do you think will work well?
How much would the DAO need to pay to make it worthwhile for teams to develop clients?
We invite existing client teams to share some stats of their development and maintenance costs. Based on these numbers the DAO can pick some reasonable amounts to set as payouts.
We wanted to share some interim thoughts we had on how to implement this, and we welcome any feedback.
These are the requirements we had in mind so far:
Changing the amounts and formulas for payouts should not require an audit, i.e. should be outside the core DAO contracts
It should be possible to delegate the ability to change the payout scheme to a trusted multisig, e.g. Tech Grants Pod.
Small (ideally zero) additional gas cost for interacting with contracts.
In order to meet requirements (1) & (2), we imagine the payout being outside of the core DAO contracts. The DAO would explicitly fund this side contract via proposals.
The core DAO contracts would introduce a new client parameter in the relevant functions, which would be either an ID or a wallet address to which to send payouts.
Regarding gas costs, we considered some methods that wouldn’t require writing to storage during the interactions:
Send a message to an L2 with the client id and interaction details; write it to storage on L2; payout calculation and disbursement on L2.
Use storage-proofs to provide evidence of the client ids to a contract on mainnet or an L2.
Both of these options add a certain degree of complexity and dependency on third parties.At the moment we’re leaning towards keeping everything on mainnet if it’s not too gas intensive.
For those interested, see a rough estimate for gas costs at the bottom of this post.
Please share your thoughts and feedback with us. Any ideas on better economics or technical solutions are very welcome.
We’re currently leaning towards implementing everything on mainnet for simplicity. Let us know if you think there’s a better way.
We want to hear your feedback before we move on to implementation:
Do you think our ideas to mitigate gaming are good? Should the DAO keep a list of approved clients? Do you have better ideas?
We plan to deploy everything on mainnet. Do you think we should use an L2 instead?
We want to focus on proposals, voting & auction bids at first. Do you think the V1 scope should be different?
What do you think would be the best payouts schemes?
How much would the DAO need to pay to make it worthwhile for teams to develop and maintain clients?
A rough estimate of the additional gas cost based on the pseudocode below:
AuctionHouse.createBid
One expensive SSTORE* per auction (setting a zeroed storage slot). By default that would be done by the first bidder, but we could also move it to be done by the settleCurrentAndCreateNewAuction function.
Subsequent bids have minimal extra gas cost
DAO.propose
One expensive SSTORE per proposal to store the client address.
The current storage layout of proposals is not optimized so we can probably “squeeze-in” the client parameter without incurring extra gas cost.
DAO.castVote
One expensive SSTORE per client per proposal: the first vote cast from a client.
This can be reimbursed as part of vote refunds, so the cost is shifted to the DAO.
*expensive SSTORE: storage write of a non-zero value into a zero slot; about 20K gas, which at 100 gwei gas price and ETH at $1600 would be ~$3.2.
Below is some rough pseudocode of the changes required in the DAO’s core contracts to save the necessary data onchain:
createBid(uint nounId, address client) {
biddingClient[nounId] = client;
// regular bid code continues...
}
propose(targets, values, signatures, calldatas, description, address client) {
clientProposals[proposalId] = client;
// regular propose code continues...
}
castVoteWithReason(uint proposalId, uint support, string reason, address client) {
// record how many votes were cast per client, per proposal
clientVotes[proposalId][client].votes += votes;
// record how many voting txs were made per client, per proposal
clientVotes[proposalId][client].txs += 1;
// regular voting code continues...
}
Example pseudocode of a payout contract:
payoutForWinningBid(uint nounId) {
require(!payoutDone[nounId], ‘already paid’);
require(auctionHouse.auction().nounId > nounId, ‘auction not settled’);
payoutDone[nounId] = true;
// send fixed amount as payout
// to send a % of auction settlement price, we need to store the history of auction prices, which we plan to do in the next version of auction-house
sendETH(auctionHouse.biddingClient[nounId], PAYOUT_FOR_BIDDING);
}
payoutForProposal(uint proposalId) {
require(!payoutForProposalPaid[proposalId], ‘already paid’);
require(dao.state(proposalId) == EXECUTED);
payoutForProposalPaid[proposalId] = true;
// send fixed amount to the client
sendETH(dao.clientProposals[proposalId], PAYOUT_FOR_PROPOSAL);
}
payoutForVotes(uint proposalId, address client) {
require(!payoutForVotesPaid[proposalId], ‘already paid’);
require(state(proposalId) == EXECUTED);
payoutForVotesPaid[proposalId] = true;
Proposal proposal = dao.proposals(proposalId);
uint totalVotes = proposal.forVotes + proposal.againstVotes + proposal.abstainVotes;
sendETH(client, (dao.clientVotes[proposalId][client] / totalVotes) * PAYOUT_FOR_VOTES_PER_PROPOSAL);
}
Like any other protocol, Nouns DAO relies on clients (webapps / mobile) to interact with its smart contracts. There are many reasons for the DAO to benefit from having multiple clients available, to name a few:
Redundancy of clients reduces risk of technical issues limiting access
The ability to sunset or stop developing nouns.wtf which is currently being maintained by Nounders
Gives space for new clients to emerge, including specialized clients like a proposal builder
Currently nouns.wtf is in an awkward place where it’s still probably the most popular client, but at the same time it’s not being actively maintained. It’s also not entirely clear how decisions are made on what can be added to the site.
We want to propose a model where clients get paid by the DAO based on usage. By usage we mean interactions with the smart contracts such as bidding on auctions, creating proposals, and voting. This is similar to the way Liquity incentivizes frontends with “kickbacks”.
We hope this will create the following incentives:
Incentivize clients to provide great UX (and do some minimum marketing) to drive more usage to their apps.
Incentivizes clients to solve for all the interactions the DAO pays for (and we can pay more for interactions with insufficient coverage).
Incentivizes clients to keep up with DAO upgrades, so they remain functional and continue to be used.
The simplest way this can work is by adding a client parameter to the function calls, e.g.:
castVote(proposalId, support, reason, client) where client is the wallet address of the client that facilitated this transaction.
Let’s discuss the key interactions we think are worth paying for, how they could be paid, and how to make sure they can’t be gamed.
We don’t want to pay for every createBid call because that can be easily gamed by making many low value bids. One way to get around it is to only pay for the interaction of the winning bid.
Fixed percentage of the auction settlement amount.
Fixed ETH amount.
Other options you think make sense? Please let us know
Having great proposal creation tools is important, supporting good previews, different transaction types, etc.
We don’t want to pay for every proposal creation because it may incentivize people to create spam proposals just to get the payment.
A few options on how to avoid it:
Only pay for successful proposals that get executed.
Only pay for proposals that pass quorum.
Let us know if you have more ideas.
Fixed ETH amount per proposal.
Fixed percentage of the amount of funding asked in the proposal. This is problematic because it creates weird incentives, and not all proposals are directly asking for funding.
Allocate a percentage of the DAO’s income to pay for proposals created, e.g. X% of auction revenue per month is used to pay governance clients for new valid proposals (valid meaning passed anti-gaming filters mentioned above).
Pay out disproportionately to top clients (e.g. 1st place gets 2x more than 2nd place).
In order to prevent spam proposals created just for voting payments, we can use the same method as mentioned above, i.e. only count votes made on “valid” proposals.
Fixed ETH amount per vote transaction.
Fixed ETH amount per vote, i.e. if voting with 2 nouns, client gets paid double.
Fixed ETH amount per proposal: e.g. if 1 ETH is paid out per proposal, and a proposal had 100 votes, then for each vote, the originating client gets 0.01 ETH.
We think the interactions listed above are the key interactions the DAO should pay for and those should be in the scope of the first iteration of this model.
Other interactions and possible payment options:
Proposal updates (updateProposal)
We don’t want to incentivize updating proposals for payouts because that’s easily gamed by doing no-op updates. Perhaps it makes sense to pay the client which originated the most recent version of a proposal. If a proposal was updated, we would update the originating client for that proposal. It makes sense for clients to support both functionalities.
Proposal candidates
All the interactions related to candidates have the goal of eventually turning the candidate into a Proposal via proposeBySigs.
Payouts for proposal creation provide incentive for clients to support all the functionality of proposal candidates.
Assuming this is enough incentive, perhaps the specific candidate interactions (creating, updating, signing, feedback) don’t need payouts.
Delegation
Don’t have a good idea on how to prevent spam delegations for payouts. Open to suggestions!
Forking
Probably makes sense for fork-related interactions to be paid for by both OG DAO and the fork DAO.
Probably makes sense to pay per forking Noun, whether put in escrow or joining after fork DAO creation; this allows multiple clients to contribute to a single fork event and be fairly compensated.
How much should be paid per forking Noun? Should it be a nominal amount, e.g. 0.01 ETH? Or should it be a percentage of BV? Any other ideas?
A more general way to try to game the system, is to pretend to be a client by putting your own wallet address when interacting with the DAO contracts. This gets worse if people actually use clients, but swap the client address with their own when posting transactions.
One way to mitigate this risk is for the DAO to maintain a list of approved clients that can be paid. Whenever a new client wants to be added to this list, they can put up a proposal. It also serves as a way to advertise that this client exists.
Alternatively there could be some algorithmic way to determine that a client is likely not fake, e.g. condition payout on a minimum number of interactions, from different wallets, etc.
We’d like your feedback here. Do you think it’s better to keep a list of approved clients or rather take the permissionless route of codifying some rules to identify “real” clients? If the latter, what rules do you think will work well?
How much would the DAO need to pay to make it worthwhile for teams to develop clients?
We invite existing client teams to share some stats of their development and maintenance costs. Based on these numbers the DAO can pick some reasonable amounts to set as payouts.
We wanted to share some interim thoughts we had on how to implement this, and we welcome any feedback.
These are the requirements we had in mind so far:
Changing the amounts and formulas for payouts should not require an audit, i.e. should be outside the core DAO contracts
It should be possible to delegate the ability to change the payout scheme to a trusted multisig, e.g. Tech Grants Pod.
Small (ideally zero) additional gas cost for interacting with contracts.
In order to meet requirements (1) & (2), we imagine the payout being outside of the core DAO contracts. The DAO would explicitly fund this side contract via proposals.
The core DAO contracts would introduce a new client parameter in the relevant functions, which would be either an ID or a wallet address to which to send payouts.
Regarding gas costs, we considered some methods that wouldn’t require writing to storage during the interactions:
Send a message to an L2 with the client id and interaction details; write it to storage on L2; payout calculation and disbursement on L2.
Use storage-proofs to provide evidence of the client ids to a contract on mainnet or an L2.
Both of these options add a certain degree of complexity and dependency on third parties.At the moment we’re leaning towards keeping everything on mainnet if it’s not too gas intensive.
For those interested, see a rough estimate for gas costs at the bottom of this post.
Please share your thoughts and feedback with us. Any ideas on better economics or technical solutions are very welcome.
We’re currently leaning towards implementing everything on mainnet for simplicity. Let us know if you think there’s a better way.
We want to hear your feedback before we move on to implementation:
Do you think our ideas to mitigate gaming are good? Should the DAO keep a list of approved clients? Do you have better ideas?
We plan to deploy everything on mainnet. Do you think we should use an L2 instead?
We want to focus on proposals, voting & auction bids at first. Do you think the V1 scope should be different?
What do you think would be the best payouts schemes?
How much would the DAO need to pay to make it worthwhile for teams to develop and maintain clients?
A rough estimate of the additional gas cost based on the pseudocode below:
AuctionHouse.createBid
One expensive SSTORE* per auction (setting a zeroed storage slot). By default that would be done by the first bidder, but we could also move it to be done by the settleCurrentAndCreateNewAuction function.
Subsequent bids have minimal extra gas cost
DAO.propose
One expensive SSTORE per proposal to store the client address.
The current storage layout of proposals is not optimized so we can probably “squeeze-in” the client parameter without incurring extra gas cost.
DAO.castVote
One expensive SSTORE per client per proposal: the first vote cast from a client.
This can be reimbursed as part of vote refunds, so the cost is shifted to the DAO.
*expensive SSTORE: storage write of a non-zero value into a zero slot; about 20K gas, which at 100 gwei gas price and ETH at $1600 would be ~$3.2.
Below is some rough pseudocode of the changes required in the DAO’s core contracts to save the necessary data onchain:
createBid(uint nounId, address client) {
biddingClient[nounId] = client;
// regular bid code continues...
}
propose(targets, values, signatures, calldatas, description, address client) {
clientProposals[proposalId] = client;
// regular propose code continues...
}
castVoteWithReason(uint proposalId, uint support, string reason, address client) {
// record how many votes were cast per client, per proposal
clientVotes[proposalId][client].votes += votes;
// record how many voting txs were made per client, per proposal
clientVotes[proposalId][client].txs += 1;
// regular voting code continues...
}
Example pseudocode of a payout contract:
payoutForWinningBid(uint nounId) {
require(!payoutDone[nounId], ‘already paid’);
require(auctionHouse.auction().nounId > nounId, ‘auction not settled’);
payoutDone[nounId] = true;
// send fixed amount as payout
// to send a % of auction settlement price, we need to store the history of auction prices, which we plan to do in the next version of auction-house
sendETH(auctionHouse.biddingClient[nounId], PAYOUT_FOR_BIDDING);
}
payoutForProposal(uint proposalId) {
require(!payoutForProposalPaid[proposalId], ‘already paid’);
require(dao.state(proposalId) == EXECUTED);
payoutForProposalPaid[proposalId] = true;
// send fixed amount to the client
sendETH(dao.clientProposals[proposalId], PAYOUT_FOR_PROPOSAL);
}
payoutForVotes(uint proposalId, address client) {
require(!payoutForVotesPaid[proposalId], ‘already paid’);
require(state(proposalId) == EXECUTED);
payoutForVotesPaid[proposalId] = true;
Proposal proposal = dao.proposals(proposalId);
uint totalVotes = proposal.forVotes + proposal.againstVotes + proposal.abstainVotes;
sendETH(client, (dao.clientVotes[proposalId][client] / totalVotes) * PAYOUT_FOR_VOTES_PER_PROPOSAL);
}
No activity yet