├── README.md ├── specification-template.md ├── LICENSE ├── vote-refund └── README.md ├── dynamic-quorum └── README.md ├── descriptor-v2 └── README.md └── payment-in-stablecoins └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Nouns Technical Specifications 2 | 3 | All specifications must follow the [specification template](./specification-template.md). 4 | -------------------------------------------------------------------------------- /specification-template.md: -------------------------------------------------------------------------------- 1 | # Noun Protocol Specification 2 | 3 | ## Simple Summary 4 | 5 | Simply describe the outcome of the change. This should be non-technical and accessible to a casual community member. 6 | 7 | ## Abstract 8 | 9 | A short (~200 word) description of the change, the abstract should clearly describe the change. This is what _will_ be done if it is implemented, not _why_ it should be done or _how_ it will be done. 10 | 11 | ## Technical Specification 12 | 13 | ### Overview 14 | 15 | The technical specification overview should outline the public API of the changes. That is, changes to any of the interfaces Nouns currently exposes or the creation of new ones. Clearly describe _how_ the specification should be implemented. 16 | 17 | ### Implementation 18 | 19 | Link to the implementation and test cases for review. 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Nouns 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /vote-refund/README.md: -------------------------------------------------------------------------------- 1 | # Vote Refund 2 | 3 | ## Simple Summary 4 | 5 | Atomically refund vote transaction costs to governance participants. 6 | 7 | ## Abstract 8 | 9 | This specification introduces four functions to the governance logic contract. These include two additional vote functions, which atomically refund vote transaction costs to governance participants, an ether withdrawal function, and a receive ether function. 10 | 11 | The governance logic contract will need to be filled with ETH in order to send refunds. As an example, this can be done by sending ETH from the treasury via a proposal. 12 | 13 | This feature will NOT refund voters for past transaction costs. 14 | 15 | ## Technical Specification 16 | 17 | ### Overview 18 | 19 | Vote Functions: 20 | 21 | 1. `castRefundableVote` - Cast a refundable vote for a proposal. 22 | 2. `castRefundableVoteWithReason` - Cast a refundable vote for a proposal with a reason. 23 | 24 | Rules: 25 | 26 | 1. `msg.sender` must only be refunded if they have at least one vote. This prevents the depletion of refund reserves by non-vote holders at no cost. 27 | 2. The gas price used in the refund calculation must be capped using a `MAX_REFUND_PRIORITY_FEE` constant. This enables the transaction to succeed while providing a partial refund if a high priority fee is used. 28 | 3. No refund will be provided if the governance contract has no balance. 29 | 4. A partial refund will be provided if the contract has a non-zero balance that is less than the full refund amount. 30 | 5. Refund transfer failures must be ignored. The transaction must succeed even though no refund has been provided. 31 | 32 | --- 33 | 34 | #### Withdrawal Function: 35 | 36 | 1. `withdraw` - Withdraw the ETH balance to the caller. 37 | 38 | Rules: 39 | 40 | 1. This function must transfer the entire ETH balance to the caller. 41 | 2. This function must only be callable by the timelock (treasury). 42 | 43 | --- 44 | 45 | #### Receive Function 46 | 47 | 1. `receive` (keyword) - Enable the governance contract to receive ETH. 48 | 49 | Rules: 50 | 51 | 1. The `receive` function must be empty. 52 | 53 | ### Implementation 54 | 55 | Not Started. 56 | -------------------------------------------------------------------------------- /dynamic-quorum/README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Governance Quorum 2 | 3 | ## Simple Summary 4 | 5 | A dynamic vote quorum that adjusts as a result of opposition, allowing uncontested proposals to pass at a minimum defined quorum, while requiring higher assurance on contested proposals. 6 | 7 | ## Abstract 8 | 9 | This specification defines an upgrade to the `NounsDAOLogicV1` contract that replaces the static `proposal.quorumVotes` variable with a function that adjusts the vote quorum between a defined minimum and maximum as a result of voter opposition. 10 | 11 | ## Technical Specification 12 | 13 | ### Overview 14 | 15 | The dynamic quorum must be implemented as an upgrade to the Nouns DAO governance logic contract (`NounsDAOLogicV2`). 16 | 17 | - The following configuration values must be implemented as storage variables: 18 | - Values: 19 | - **Minimum Quorum Votes Basis Points** - The minimum basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed. 20 | - **Maximum Quorum Votes Basis Points** - The maximum basis point number of votes in support of a proposal required in order for a quorum to be reached and for a vote to succeed. 21 | - **Dynamic Quorum Linear Coefficient** - The coefficient used to control the dynamic quorum slope. 22 | - Rules: 23 | - These values must be **checkpointed** by block number upon update. A publicly exposed function must allow for historical configuration values to be read. 24 | - Minimum quorum votes BPS must be guarded by constant lower and upper bounds. 25 | - Maximum quorum votes BPS must be greater than or equal to the minimum and be guarded by a constant upper bound. 26 | - The `totalSupply` at the time of proposal creation must be stored for use by the dynamic quorum function. 27 | - The `state` function, which determines proposal outcomes, must consume the `quorumVotes` public view function. Previously, the `state` function consumed the static `proposal.quorumVotes` value that was assigned during proposal creation. 28 | - `quorumVotes` Rules: 29 | - Given a proposal ID, the `quorumVotes` function must return the number of votes required for the proposal to succeed. 30 | - `quorumVotes` must return the minimum quorum if the proposal was created prior to the deployment of `NounsDAOLogicV2`. 31 | - `quorumVotes` must read the checkpointed configuration values from the proposal creation block. 32 | - The dynamic quorum function is as follows (pseudocode): 33 | ```js 34 | /* 35 | DAO-Controlled Configuration Values: 36 | 37 | min_quorum_bps - Minimum quorum in basis points 38 | max_quorum_bps - Maximum quorum in basis points 39 | linear_coefficient - Adjust the slope of the dynamic quorum 40 | 41 | Dynamic Quorum Calculation: 42 | 43 | quorum = min((min_quorum_bps + bx) / 10_000, max_quorum) 44 | 45 | Reference: 46 | */ 47 | 48 | function get_percent(bps, num) { 49 | return (bps * num) / 10_000 50 | } 51 | function get_quorum_adjustment_bps(against_votes_adjusted_bps) { 52 | return linear_coefficient * against_votes_adjusted_bps 53 | } 54 | function max_quorum() { 55 | return get_percent(max_quorum_bps, total_supply) 56 | } 57 | 58 | quorum_adjustment_bps = get_quorum_adjustment_bps(against_votes_bps) 59 | adjusted_quorum = get_percent(min_quorum_bps + quorum_adjustment_bps, total_supply) 60 | quorum = min(adjusted_quorum, max_quorum) 61 | ``` 62 | - `_setQuorumVotesBPS` must be removed as it no longer serves a purpose. 63 | - `minQuorumVotes` must be exposed as an external view function, which returns the current minimum quorum votes using Noun total supply. 64 | - `maxQuorumVotes` must be exposed as an external view function, which returns the current maximum quorum votes using Noun total supply. 65 | 66 | A dynamic quorum simulation can be found [here](https://docs.google.com/spreadsheets/d/1trmtpgSLx8cfm-wD79mQWyh2y_GwW3X7f2rEnSWJ3AM/edit?usp=sharing), courtesy of the Verbs team. 67 | 68 | ### Implementation 69 | 70 | In Progress. 71 | -------------------------------------------------------------------------------- /descriptor-v2/README.md: -------------------------------------------------------------------------------- 1 | # Nouns Descriptor V2 2 | 3 | ## Simple Summary 4 | 5 | A new descriptor version that allows for significantly cheaper deployment of the Nouns art and better composability by improving the external API. 6 | 7 | ## Abstract 8 | 9 | This specification defines a new version of the `NounsDescriptor` contract. The new contracts provides the following benefits: 10 | 11 | - Cheaper deployment cost: ~13.8M gas instead of ~67.3M gas 12 | - Easier composability for external contracts by splitting the tokenURI construction to several contracts 13 | 14 | ## Technical Specification 15 | 16 | ### Overview 17 | 18 | A combination of several techniques will be used in order to reduce the storage size and cost required for the art: 19 | 20 | 1. RLE encoding supporting multilines 21 | 2. Compressing the data using DEFLATE 22 | 3. Storing data using `SSTORE2` 23 | 24 | #### RLE encoding supporting multilines 25 | 26 | The current implementation of the RLE encoding is limited to the length of a row, i.e if it is encoding 2 rows of blue pixels, the encoding will have one RLE per row. 27 | By allowing the encoding to span across several rows, the encoding can be more effecient. 28 | 29 | Note that each RLE tuple is limited to a length of 255. If a run-length is greater than 255, it must be broken to multiple tuples. 30 | 31 | This will require: 32 | 33 | 1. Updating the encoder (javascript code) to allow encoding multiple lines 34 | 2. Updating the decoded (smart contract code) to support multi-line encoding. Currently this is in `MultiPartRLEToSVG.sol` 35 | 36 | #### Compressing the data using DEFLATE 37 | 38 | 1. Compression 39 | 40 | The RLE encoded images can be further compressed by using the general purpose [DEFLATE](https://en.wikipedia.org/wiki/Deflate) compression algorithm. 41 | 42 | In order to maximize the compression effeciency, the images will be compressed in batch. Specifically, all images of a certain trait type (e.g. heads) will be compressed together. 43 | 44 | In each batch, the images will be abi-encoded as `bytes[]` prior to the compression. 45 | 46 | Example javascript code of abi-encoding & compression using deflate: 47 | 48 | ```js 49 | import { deflateRawSync } from "zlib"; 50 | 51 | // data is an array of hexlified byte strings, each starting with 0x 52 | const abiEncoded = ethers.utils.defaultAbiCoder.encode(["bytes[]"], [data]); 53 | const encodedCompressed = `0x${deflateRawSync( 54 | Buffer.from(abiEncoded.substring(2), "hex") 55 | ).toString("hex")}`; 56 | ``` 57 | 58 | 2. Decompression 59 | 60 | To decompress the data, a solidity library implementing the [Puff](https://github.com/madler/zlib/tree/master/contrib/puff) decompression algorithm will be used. It will be a slightly modified version of [inflate-sol](https://github.com/adlerjohn/inflate-sol) that introduces gas saving modifications. 61 | 62 | #### Storing data using `SSTORE2` 63 | 64 | Use the [SSTORE2](https://github.com/Rari-Capital/solmate/blob/main/src/utils/SSTORE2.sol) library for writing and reading the images data. This reduces the cost of storage. 65 | 66 | #### Contracts changes 67 | 68 | 1. `INounsDescriptor` 69 | 70 | 1.1. `palettes` 71 | 72 | ```diff 73 | - function palettes(uint8 paletteIndex, uint256 colorIndex) external view returns (string memory); 74 | - function addColorToPalette(uint8 paletteIndex, string calldata color) external; 75 | 76 | 77 | + /// @return Encoded color palette: every 3 bytes represent an RGB color, e.g 0x112233 = #112233 78 | + function palettes(uint8 paletteIndex) external view returns (bytes memory); 79 | 80 | + function setPalette(uint8 paletteIndex, bytes calldata palette) external; 81 | ``` 82 | 83 | 1.2. Change functions for adding new images, shown here only for the body trait, but applies for heads, glasses & accessories: 84 | 85 | ```diff 86 | - function addManyBodies(bytes[] calldata bodies) external; 87 | - function addBody(bytes calldata body) external; 88 | 89 | + /** 90 | + * @notice Add a batch of body images. 91 | + * @param encodedCompressed bytes created by taking a string array of RLE-encoded images, abi encoding it as a bytes array, 92 | + * and finally compressing it using deflate. 93 | + * @param decompressedLength the size in bytes the images bytes were prior to compression; required input for Inflate. 94 | + * @param imageCount the number of images in this batch; used when searching for images among batches. 95 | + */ 96 | + function addBodies( 97 | + bytes calldata encodedCompressed, 98 | + uint80 decompressedLength, 99 | + uint16 imageCount 100 | + ) external; 101 | 102 | + /** 103 | + * @notice Add a batch of body images from an existing storage contract. 104 | + * @param pointer the address of a contract where the image batch was stored using SSTORE2. The data 105 | + * format is expected to be like {encodedCompressed}: bytes created by taking a string array of 106 | + * RLE-encoded images, abi encoding it as a bytes array, and finally compressing it using deflate. 107 | + * @param decompressedLength the size in bytes the images bytes were prior to compression; required input for Inflate. 108 | + * @param imageCount the number of images in this batch; used when searching for images among batches. 109 | + */ 110 | + function addBodiesFromPointer( 111 | + address pointer, 112 | + uint80 decompressedLength, 113 | + uint16 imageCount 114 | + ) external; 115 | ``` 116 | 117 | All functions that add images will have access control restriction to the contract owner. 118 | 119 | 2. `NounsDescriptor` 120 | 121 | The `constructor` will get addresses for 2 contracts, one implementing `INounsArt` and one implementing `ISVGRenderer`. 122 | 123 | 3. `INounsArt` 124 | 125 | ```js 126 | function addManyBackgrounds(string[] calldata _backgrounds) external; 127 | function addBackground(string calldata _background) external; 128 | function palettes(uint8 paletteIndex) external view returns (bytes memory); 129 | function setPalette(uint8 paletteIndex, bytes calldata palette) external; 130 | 131 | function addBodies( 132 | bytes calldata encodedCompressed, 133 | uint80 decompressedLength, 134 | uint16 imageCount 135 | ) external; 136 | 137 | function addBodiesFromPointer( 138 | address pointer, 139 | uint80 decompressedLength, 140 | uint16 imageCount 141 | ) external; 142 | 143 | /// @notice A batch of compressed images 144 | struct NounArtStoragePage { 145 | /// @dev Number of images in this page 146 | uint16 imageCount; 147 | /// @dev Length of decompressed data, needed for decompression 148 | uint80 decompressedLength; 149 | /// @dev Pointer to the stored data to be read with SSTORE2 150 | address pointer; 151 | } 152 | 153 | struct Trait { 154 | /// @notice Array of pages, each holding a batch of images 155 | NounArtStoragePage[] storagePages; 156 | /// @notice Total stored images across all pages 157 | uint256 storedImagesCount; 158 | } 159 | 160 | /// Same functions below for other traits (heads, glasses, accessories) 161 | 162 | /// @notice returns an RLE encoded image of body trait 163 | function bodies(uint256 storageIndex) external view returns (bytes memory); 164 | 165 | /// @notice Returns a Trait object for the body trait images 166 | function bodiesTrait() external view returns (Trait memory); 167 | 168 | /// @notice Returns the number of pages in the body trait Trait object 169 | function bodiesPageCount() external view returns (uint256); 170 | 171 | /// @notice Returns page number `pageIndex` of the body trait Trait object 172 | function bodiesPage(uint256 pageIndex) external view returns (INounsArt.NounArtStoragePage memory); 173 | 174 | ``` 175 | 176 | The `NounsArt` contract will be responsible for storing and retrieving the images. 177 | Changes to the images will be access controlled and restricted to the active `NounsDescriptor`. 178 | 179 | Each time a batch of images is added for a certain trait, a new "page" is created and represented by `NounArtStoragePage`. This page is then added to the `storedPages` member of the `Trait` struct for the "body" trait. 180 | Same behavior for the other traits (heads, glasses, accessories). 181 | 182 | 4. `NFTDescriptor` 183 | 184 | - The public functions now receive an `ISVGRenderer` as a parameter. 185 | - The palette is now passed as part of the `TokenURIParams`. 186 | 187 | ```diff 188 | - function constructTokenURI(TokenURIParams memory params, mapping(uint8 => string[]) storage palettes) public view returns (string memory) 189 | + function constructTokenURI(ISVGRenderer renderer, TokenURIParams memory params) public view returns (string memory) 190 | 191 | - function generateSVGImage(MultiPartRLEToSVG.SVGParams memory params, mapping(uint8 => string[]) storage palettes) public view returns (string memory) 192 | + function generateSVGImage(ISVGRenderer renderer, ISVGRenderer.SVGParams memory params) public view returns (string memory) 193 | ``` 194 | 195 | 5. `ISVGRenderer` 196 | 197 | `SVGRenderer` is responsible for taking RLE encoded image parts and constructing an SVG. 198 | This is similar to the code currently in the `MultiPartRLEToSVG.sol` library. 199 | 200 | ```js 201 | struct Part { 202 | /// @dev RLE encoded image 203 | bytes image; 204 | bytes palette; 205 | } 206 | 207 | struct SVGParams { 208 | Part[] parts; 209 | string background; 210 | } 211 | 212 | function generateSVG(SVGParams memory params) external view returns (string memory svg); 213 | function generateSVGPart(Part memory part) external view returns (string memory partialSVG); 214 | function generateSVGParts(Part[] memory parts) external view returns (string memory partialSVG); 215 | ``` 216 | 217 | ### Implementation 218 | 219 | Not started. 220 | -------------------------------------------------------------------------------- /payment-in-stablecoins/README.md: -------------------------------------------------------------------------------- 1 | # Proposal Payment in Stablecoins 2 | 3 | ## Simple Summary 4 | 5 | Allows the DAO to execute proposals that send funds denominated in USD. This change addresses the ETH/USD price volatility risk both sides suffer from today, as well as the DAO's ability to perform ETH-to-USD swaps trustlessly. 6 | 7 | ### Simple User flow 8 | 9 | 1. Proposal creation: new UI will allow proposer to choose to pay out in USD and specify the amount, e.g. 50K USD 10 | 2. Proposal execution: builder receives the desired USD amount 11 | 1. If the DAO doesn't have sufficient USD, a debt is registered on chain, and shortly after a trading bot would sell the DAO the outstanding USD and immediately close the builder's balance 12 | 13 | ### Detailed User Flow 14 | 15 | TokenBuyer is open to trading whenever: 16 | 17 | 1. its USD balance is below the desired position, or 18 | 2. the DAO has a debt towards a builder 19 | 20 | When TokenBuyer is open to trading, it offers trading bots the opportunity to sell their stablecoins (e.g. USDC) in exchange for ETH. Bots are incentivized by offering a price premium, providing them an arbitrage opportunity. 21 | 22 | Since acquiring USD from bots can take time, we expect the proposal page to clearly show proposal builders their outstanding balance and explain the balance should be settled shortly. 23 | 24 | TokenBuyer's USD position (baseline amount) configuration value helps mitigate debt delays: the higher the position, the less proposals will ever have to enter debt state. 25 | 26 | ## Abstract 27 | 28 | This specification introduces two new contracts that facilitate stablecoin payments: 29 | 30 | 1. `TokenBuyer`, a contract that: 31 | 32 | 1. allows anyone to sell it stablecoins in exchange for ETH with some additional incentive 33 | 2. funds the `Payer` contract with said stablecoins 34 | 3. auto-pays `Payer` debts upon receiving stablecoins 35 | 36 | 2. `Payer`, a contract that: 37 | 1. allows the DAO to pay builders in stablecoins (e.g. USDC) 38 | 2. registers debt when the DAO's obligations exceed its stablecoin balance 39 | 3. allows anyone to run its outstanding debt payment function 40 | 41 | ## Technical Specification 42 | 43 | ### Overview 44 | 45 | #### Interaction flow 46 | 47 | 1. A proposal is created, where the DAO wants to pay the builder $100K, with the following transactions 48 | 1. `Payer.sendOrRegisterDebt(recipient, 100_000)` 49 | 2. send TokenBuyer ETH at the amount of `TokenBuyer.ethNeeded(100_000, 2)` (assuming we want a 2x volatility buffer) 50 | 2. Upon proposal execution: 51 | 1. Proposal recipient receives 100K stablecoins (e.g. USDC) 52 | 2. 133.33 ETH is sent from the treasury to `TokenBuyer` (assuming ETH/USD is $1500 and 2x buffer above, and assuming Payer's balance is $0) 53 | 3. Arbitrageurs sell USD to `TokenBuyer` in exchange for ETH: 54 | 1. They call `TokenBuyer.buyETH(100_000)` (or smaller amounts across multiple txs) 55 | 1. Expected to happen when `TokenBuyer.price()` is better than other DEX prices 56 | 2. `Payer`'s USD balance grows by $100K, and an appropriate ETH balance is spent 57 | 58 | ### `TokenBuyer` contract 59 | 60 | A new contract that helps the DAO acquire stablecoins by buying them from bots for a margin. 61 | 62 | #### Contract state variables 63 | 64 | Immutable: 65 | 66 | - `paymentToken` the ERC20 token to use as USD, e.g. USDC 67 | - `paymentTokenDecimalsDigits` as `10^paymentToken.decimals()`, used in conversions between token decimals and ETH decimals (18) 68 | 69 | Mutable: 70 | 71 | - `admin` contract admin, allowed to do certain lower risk operations 72 | - `payer` the `Payer` contract it helps fund with stablecoins 73 | - `priceFeed` the address of the `IPriceFeed` contract used to fetch ETH/stablecoin prices 74 | - `baselineStablecoinAmount` The minimum `paymentToken` balance the `payer` contract should have 75 | - `minAdminBaselinePaymentTokenAmount` The minimum value for `baselinePaymentTokenAmount` that `admin` is allowed to set 76 | - `maxAdminBaselinePaymentTokenAmount` The maximum value for `baselinePaymentTokenAmount` that `admin` is allowed to set 77 | - `botDiscountBPs` the amount of basis points to decrease the price by, to increase the incentive to transact with this contract 78 | - `minAdminBotDiscountBPs` The minimum value for `botDiscountBPs` that `admin` is allowed to set 79 | - `maxAdminBotDiscountBPs` The maximum value for `botDiscountBPs` that `admin` is allowed to set 80 | 81 | #### View functions 82 | 83 | `function ethNeeded(uint256 additionalTokens, uint256 bufferBPs) public view returns (uint256)` 84 | 85 | - Get how much ETH this contract needs in order to fund its current obligations plus `additionalTokens`, with a safety buffer `bufferBPs` basis points 86 | 87 | `function tokenAmountNeeded() public view returns (uint256)` 88 | 89 | - Returns the amount of tokens this contract is willing to exchange of ETH 90 | - zero if it has enough tokens 91 | - otherwise `baselinePaymentTokenAmount + payer.totalDebt() - paymentToken.balanceOf(address(payer))` 92 | 93 | `function price() public view returns (uint256)` 94 | 95 | - Returns the ETH/`paymentToken` price this contract is willing to exchange ETH at, including the discount 96 | 97 | `function ethAmountPerTokenAmount(uint256 tokenAmount) public view returns (uint256)` 98 | 99 | - Returns the amount of ETH this contract will send in exchange for `tokenAmount` tokens 100 | 101 | `function tokenAmountNeededAndETHPayout() public view returns (uint256, uint256)` 102 | 103 | - Returns the amount of tokens the contract can buy and the amount of ETH it will pay for it 104 | 105 | `function tokenAmountPerEthAmount(uint256 ethAmount) public view returns (uint256)` 106 | 107 | - Returns the amount of tokens the contract expects in return for eth 108 | 109 | #### `buyETH` transaction 110 | 111 | ##### Description 112 | 113 | - Buy ETH from this contract in exchange for `paymentToken` tokens 114 | - The price is determined using `priceFeed` plus `botDiscountBPs` 115 | - Immediately invokes `payer` to pay back outstanding debt 116 | 117 | ##### Rules 118 | 119 | `function buyETH(uint256 tokenAmount)` 120 | 121 | - `msg.sender` must approve this contract to spend `tokenAmount` of stablecoin, which it will transfer to `Payer` 122 | - should revert if contract has insufficient ETH balance 123 | 124 | `function buyETH(uint256 tokenAmount, address to, bytes calldata data)` 125 | 126 | - `to` must implement the interface `IBuyETHCallback.buyETHCallback` 127 | - as part of `buyETHCallback`, sender must transfer `tokenAmount` stablecoins to `Payer` 128 | - should revert if contract has insufficient ETH balance 129 | 130 | #### Admin / Owner transactions 131 | 132 | ##### General Rules 133 | 134 | - `msg.sender` must be `admin` or `owner` 135 | 136 | ##### Specific Rules 137 | 138 | `function setBotDiscountBPs(uint16 newBotDiscountBPs)` 139 | 140 | - if `msg.sender == admin`, the new value must be within the min/max bounds set by `owner` 141 | 142 | `function setBaselinePaymentTokenAmount(uint256 newBaselinePaymentTokenAmount)` 143 | 144 | - if `msg.sender == admin`, the new value must be within the min/max bounds set by `owner` 145 | 146 | `function withdrawETH()` 147 | 148 | - the contract's ETH balance must be sent to `owner` 149 | - should revert if the transfer fails 150 | 151 | `function pause()` 152 | `function unpause()` 153 | `function setAdmin(address newAdmin)` 154 | 155 | - no additional rules 156 | 157 | #### Owner-only transactions 158 | 159 | ##### Rules 160 | 161 | - `msg.sender` must be `owner` 162 | 163 | `function setMinAdminBotDiscountBPs(uint16 newMinAdminBotDiscountBPs)` 164 | 165 | `function setMaxAdminBotDiscountBPs(uint16 newMaxAdminBotDiscountBPs)` 166 | 167 | `function setMinAdminBaselinePaymentTokenAmount(uint256 newMinAdminBaselinePaymentTokenAmount)` 168 | 169 | `function setMaxAdminBaselinePaymentTokenAmount(uint256 newMaxAdminBaselinePaymentTokenAmount)` 170 | 171 | `function setPriceFeed(IPriceFeed newPriceFeed)` 172 | 173 | `function setPayer(address newPayer)` 174 | 175 | No function-specific rules. 176 | 177 | #### `receive` transaction 178 | 179 | - This contract accepts ETH, allowing the DAO to fund further stablecoin purchasing as needed 180 | - should not revert 181 | 182 | ### `Payer` contract 183 | 184 | This contract is used to pay recipients from a balance of ERC20 tokens, supporting a state of outstanding debt to recipients. 185 | 186 | #### Contract state variables 187 | 188 | Immutable: 189 | 190 | - `paymentToken` The ERC20 token used to pay users 191 | 192 | Mutable: 193 | 194 | - `totalDebt` The total debt owed, in `paymentToken` tokens 195 | - `queue` A queue of debt entries waiting to be paid 196 | 197 | #### `sendOrRegisterDebt` transaction 198 | 199 | `function sendOrRegisterDebt(address account, uint256 amount)` 200 | 201 | ##### Description 202 | 203 | - Pays `account` an `amount` of `paymentToken`s 204 | - Adds a debt entry if there's not enough tokens 205 | 206 | ##### Rules 207 | 208 | - msg.sender must be `owner` 209 | 210 | #### `payBackDebt` transaction 211 | 212 | `function payBackDebt(uint256 amount)` 213 | 214 | ##### Description 215 | 216 | - Pays back debt up to `amount` of `paymentToken` 217 | - Debt is paid in FIFO order 218 | 219 | ##### Rules 220 | 221 | - should only attempt to pay up to the contract's `paymentToken` balance 222 | 223 | ### `IPriceFeed` interface 224 | 225 | The interface `TokenBuyer` uses to fetch ETH/stablecoin prices. 226 | 227 | - `function price()` returns the latest price 228 | 229 | ### `PriceFeed` contract 230 | 231 | `PriceFeed` is the first implementation of `IPriceFeed`. It uses Chainlink as its only oracle, to avoid the extra gas costs of using multiple oracles, under the assumption that Chainlink is highly available. 232 | 233 | #### Contract state variables 234 | 235 | Immutable: 236 | 237 | - `chainlink` Chainlink price feed 238 | - `decimals` Number of decimals of the chainlink price feed answer 239 | - `decimalFactor` A factor to multiply or divide by to get to 18 decimals 240 | - `staleAfter` Max staleness allowed from chainlink, in seconds 241 | - `priceLowerBound` Sanity check: minimal price allowed 242 | - `priceUpperBound` Sanity check: maximal price allowed 243 | 244 | #### `price` view function 245 | 246 | ##### Description 247 | 248 | - Returns the price of ETH/Token by fetching from Chainlink 249 | 250 | ##### Rules 251 | 252 | - should revert if Chainlink's lastest update is older than `staleAfter` seconds ago 253 | - should revert if Chainlink's price is outside the min/max sanity bounds 254 | 255 | ### Implementation 256 | 257 | [Token Buyer repo](https://github.com/nounsDAO/token-buyer/). 258 | --------------------------------------------------------------------------------