├── .gas-snapshot ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .gitmodules ├── README.md ├── artifacts ├── build-info │ ├── 3d4f9af651c29e8af0c3eea66a51781e.json │ └── 96e5e6b1354355ca45a0252c1a4cdda9.json └── contracts │ ├── DRIP20.sol │ ├── DRIP20.dbg.json │ └── DRIP20.json │ ├── GIGADRIP20.sol │ ├── GIGADRIP20.dbg.json │ └── GIGADRIP20.json │ └── test20.sol │ ├── Test20.dbg.json │ └── Test20.json ├── foundry.toml ├── gas-report.txt ├── src ├── DRIP20.sol └── GIGADRIP20.sol └── test ├── DRIP20.t.sol ├── GIGADRIP20.t.sol └── mocks ├── MockDRIP20.sol └── MockGIGADRIP20.sol /.gas-snapshot: -------------------------------------------------------------------------------- 1 | DRIP20Test:testBurnFromDrippingUser() (gas: 101718) 2 | DRIP20Test:testDripMultiUser() (gas: 183686) 3 | DRIP20Test:testDripSingleUser() (gas: 95210) 4 | DRIP20Test:testMint() (gas: 67709) 5 | DRIP20Test:testRevertStartDrip() (gas: 85816) 6 | DRIP20Test:testRevertStopDrip() (gas: 15817) 7 | DRIP20Test:testSimpleBurn() (gas: 95507) 8 | DRIP20Test:testSimpleTransfer() (gas: 99330) 9 | DRIP20Test:testSimpleTransferFrom() (gas: 125318) 10 | DRIP20Test:testStopDripMultiUser() (gas: 193009) 11 | DRIP20Test:testStopDripSingleUser() (gas: 107080) 12 | DRIP20Test:testTransferFromDrippingUserToDrippingUser() (gas: 176876) 13 | DRIP20Test:testTransferFromDrippingUserToNonDrippingUser() (gas: 150414) 14 | DRIP20Test:testTransferFromNonDrippingUserToDrippingUser() (gas: 175058) 15 | GIGADRIP20Test:testBurnFromDrippingUser() (gas: 81507) 16 | GIGADRIP20Test:testBurnFromDrippingUserMultiplier() (gas: 121261) 17 | GIGADRIP20Test:testBurnFromNonDrippingUser() (gas: 105288) 18 | GIGADRIP20Test:testMint() (gas: 65795) 19 | GIGADRIP20Test:testSimpleBurn() (gas: 93817) 20 | GIGADRIP20Test:testSimpleTransfer() (gas: 97519) 21 | GIGADRIP20Test:testSimpleTransferFrom() (gas: 123582) 22 | GIGADRIP20Test:testStopStreamMultiUser() (gas: 177852) 23 | GIGADRIP20Test:testStopStreamMultiUserMultiplier() (gas: 198504) 24 | GIGADRIP20Test:testStopStreamRevert() (gas: 69036) 25 | GIGADRIP20Test:testStopStreamSingleUser() (gas: 99929) 26 | GIGADRIP20Test:testStreamMultiUser() (gas: 165451) 27 | GIGADRIP20Test:testStreamMultiUserMultiplier() (gas: 165460) 28 | GIGADRIP20Test:testStreamSingUserMultiDrip() (gas: 181251) 29 | GIGADRIP20Test:testStreamSingleUser() (gas: 71847) 30 | GIGADRIP20Test:testStreamSingleUserMultiplier() (gas: 71935) 31 | GIGADRIP20Test:testTransferFromDrippingUserToDrippingUser() (gas: 158033) 32 | GIGADRIP20Test:testTransferFromDrippingUserToDrippingUserMultiplier() (gas: 157934) 33 | GIGADRIP20Test:testTransferFromDrippingUserToNonDrippingUser() (gas: 130299) 34 | GIGADRIP20Test:testTransferFromDrippingUserToNonDrippingUserMultiplier() (gas: 130201) 35 | GIGADRIP20Test:testTransferFromNonDrippingUserToDrippingUser() (gas: 154817) 36 | GIGADRIP20Test:testTransferFromNonDrippingUserToDrippingUserMultiplier() (gas: 154739) 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cache/ 2 | out/ 3 | .DS_Store 4 | Agent 5 | args.js 6 | hardhat* 7 | package* 8 | script.js 9 | node_modules/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/ds-test"] 2 | path = lib/ds-test 3 | url = https://github.com/dapphub/ds-test 4 | [submodule "lib/forge-std"] 5 | path = lib/forge-std 6 | url = https://github.com/foundry-rs/forge-std 7 | [submodule "lib/DRIP20"] 8 | path = lib/DRIP20 9 | url = https://github.com/0xBeans/DRIP20 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DRIP20 2 | 3 | This is an lightweight ERC20 implementation that supports token dripping(streaming). Rather than wallets `claiming` tokens, tokens will `drip` into desired wallets at an emission rate accrued per block. 4 | 5 | TLDR; Calculates a wallet's balance based on emission rate and the block num a wallet starts accruing at. Token balances of accuring wallets will increase every block. 6 | 7 | I foresee this being the most useful in games and certain NFT projects. 8 | 9 | ## Installation / setup 10 | 11 | If using Foundry: `forge install 0xBeans/DRIP20` 12 | 13 | Else: git clone this repo. 14 | 15 | ## Contracts 16 | 17 | DRIP20.sol - ERC20 implementation that supports dripping. 18 | 19 | - Must define `_emissionRatePerBlock` which determines how many tokens will be dripped into each wallet per block. This value is immutable after intialization. 20 | - `_startDripping(address)` and `_stopDripping(address)` add and remove wallets respectively. 21 | 22 | GIGADRIP20.sol - Modification of `DRIP20.sol` that allows wallets to receive larger emissions based on a `multiplier`. 23 | 24 | - Same constructor args as `DRIP20.sol` 25 | - `_startDripping(address, multiplier)` and `_stopDripping(address, , multiplier)` increase and decrease a wallets emissions respectively. 26 | 27 | - All wallets start off with `multiplier == 0` (not receiving any token drips). Example: `_startDripping(newWallet, 1)` will increase `newWallet` multiplier to `1`, meaning it will receive drips at the `_emissionRatePerBlock`. A second txn of `_startDripping(newWallet, 3)` will add `3` to its emission rate, so now `newWallet` will have `4 * _emissionRatePerBlock` dripped into its wallet per block. 28 | - Same thing happens for `_stopDripping(address, multiplier)`, but it decreases a wallets multiplier until it goes back to 0 (no drips). 29 | 30 | For NFT projects, you can override ERC721 transfer() to call `_startDripping()` and `_stopDripping()` appropriately. 31 | 32 | ## Example Use Cases 33 | 34 | `DRIP20.sol` - Any project that is currently yielding tokens for their users - but rather than having users `claim`, it can directly be dripped into their wallets. 35 | 36 | `GIGADRIP20.sol` - NFT projects or games where each NFT yields certain amount of tokens per set time. For example, let's say `Project A` releases 10k PFPs, and each PFP earns 5 `$ATokens` per day. If a wallet has 10 PFPs, they would need to earn 5 * 10 `ATokens` per day. Rather than have the wallet claim `$ATokens` every so often, these tokens can be dripped into the wallet. For GIGADRIP20, this wallet's `multiplier` would be 5 (or however many PFPs they own) and the emission rate per block would sum up to 5 tokens a day. 37 | 38 | ## Gas usage 39 | 40 | [Here is a repo benchmarking OpenZeppelin's ERC20 implementation.](https://github.com/alephao/solidity-benchmarks/blob/main/ERC20.md) 41 | 42 | Both `DRIP20.sol` and `GIGADRIP20.sol` are very comparable in gas usage, in some scenarios even cheaper. 43 | 44 | Check out the report in `gas-report.txt` 45 | 46 | 47 | ## Security 48 | 49 | This is an experimental implementation of ERC20 and has not received a professional audit. I promise no security guarantees and will not be liable for any issues. 50 | 51 | ## Testing 52 | 53 | I use [Foundry](https://github.com/foundry-rs/foundry). 54 | 55 | ## Caveats 56 | 57 | This is the base implementation of ERC20 tokens that support dripping. So, these are things you should know: 58 | 59 | Since token dripping per block does not emit any events, token indexers such as Etherscan may not show the correct holder balances. We emit a transfer event when an address begins dripping, however, transfer events to update balances don't get emitted until an explicit transfer happens. Thus, token indexers will show the correct # of holders but balances of each holder will be a lower bound. Other rebasing tokens face this issue as well (maybe this will get fixed once indexers don't solely rely on events). 60 | 61 | There is no `maxSupply` or global `stopDripping` function. I initially designed this for a game where the game economy continues to grow and inflate. Think about Maplestory or Axie Infinity, their in-game currency doesn't have a `maxSupply` because that would stunt game growth and worsen playability (nor does it plan to prevent wallets from earning in the future). The economy just continues to grow and inflate as they add users. 62 | 63 | Also, because this is a `base` implementation, I believe the implementing contract should define the logic for `maxSupply` or the logic for stopping emissions completely. Similarly to OpenZeppelin's base ERC20 implementation, it doesn't specify a `maxSupply` and it's on the implementing contract to add this logic if desired. Tbh, You could probably write some cool functions to stop wallet streaming (since you get gas refunds on this since you're clearing storage). 64 | 65 | ## Shoutouts 66 | 67 | T11s and [Solmate](https://github.com/Rari-Capital/solmate) for the slim ERC20 implementation. 68 | [Superfluid](https://github.com/superfluid-finance) and [Proof of Humanity](https://www.proofofhumanity.id/) for the token dripping inspiration. 69 | 70 | ## Contributions 71 | 72 | Feel free to submit a PR for anything 73 | 74 | ## Todo 75 | 76 | - Potentially add EIP-2612. 77 | - Potentially have mutable emissions - would need to be careful about totalSupply() calculations and ensure proper emission block calculations. 78 | - Potentially write extensions with `maxSupply` and emission stoppage functionality. 79 | -------------------------------------------------------------------------------- /artifacts/contracts/DRIP20.sol/DRIP20.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/96e5e6b1354355ca45a0252c1a4cdda9.json" 4 | } 5 | -------------------------------------------------------------------------------- /artifacts/contracts/DRIP20.sol/DRIP20.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "DRIP20", 4 | "sourceName": "contracts/DRIP20.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "address", 12 | "name": "owner", 13 | "type": "address" 14 | }, 15 | { 16 | "indexed": true, 17 | "internalType": "address", 18 | "name": "spender", 19 | "type": "address" 20 | }, 21 | { 22 | "indexed": false, 23 | "internalType": "uint256", 24 | "name": "amount", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "Approval", 29 | "type": "event" 30 | }, 31 | { 32 | "anonymous": false, 33 | "inputs": [ 34 | { 35 | "indexed": true, 36 | "internalType": "address", 37 | "name": "from", 38 | "type": "address" 39 | }, 40 | { 41 | "indexed": true, 42 | "internalType": "address", 43 | "name": "to", 44 | "type": "address" 45 | }, 46 | { 47 | "indexed": false, 48 | "internalType": "uint256", 49 | "name": "amount", 50 | "type": "uint256" 51 | } 52 | ], 53 | "name": "Transfer", 54 | "type": "event" 55 | }, 56 | { 57 | "inputs": [ 58 | { 59 | "internalType": "address", 60 | "name": "", 61 | "type": "address" 62 | }, 63 | { 64 | "internalType": "address", 65 | "name": "", 66 | "type": "address" 67 | } 68 | ], 69 | "name": "allowance", 70 | "outputs": [ 71 | { 72 | "internalType": "uint256", 73 | "name": "", 74 | "type": "uint256" 75 | } 76 | ], 77 | "stateMutability": "view", 78 | "type": "function" 79 | }, 80 | { 81 | "inputs": [ 82 | { 83 | "internalType": "address", 84 | "name": "spender", 85 | "type": "address" 86 | }, 87 | { 88 | "internalType": "uint256", 89 | "name": "amount", 90 | "type": "uint256" 91 | } 92 | ], 93 | "name": "approve", 94 | "outputs": [ 95 | { 96 | "internalType": "bool", 97 | "name": "", 98 | "type": "bool" 99 | } 100 | ], 101 | "stateMutability": "nonpayable", 102 | "type": "function" 103 | }, 104 | { 105 | "inputs": [ 106 | { 107 | "internalType": "address", 108 | "name": "addr", 109 | "type": "address" 110 | } 111 | ], 112 | "name": "balanceOf", 113 | "outputs": [ 114 | { 115 | "internalType": "uint256", 116 | "name": "", 117 | "type": "uint256" 118 | } 119 | ], 120 | "stateMutability": "view", 121 | "type": "function" 122 | }, 123 | { 124 | "inputs": [], 125 | "name": "decimals", 126 | "outputs": [ 127 | { 128 | "internalType": "uint8", 129 | "name": "", 130 | "type": "uint8" 131 | } 132 | ], 133 | "stateMutability": "view", 134 | "type": "function" 135 | }, 136 | { 137 | "inputs": [], 138 | "name": "emissionRatePerBlock", 139 | "outputs": [ 140 | { 141 | "internalType": "uint256", 142 | "name": "", 143 | "type": "uint256" 144 | } 145 | ], 146 | "stateMutability": "view", 147 | "type": "function" 148 | }, 149 | { 150 | "inputs": [], 151 | "name": "name", 152 | "outputs": [ 153 | { 154 | "internalType": "string", 155 | "name": "", 156 | "type": "string" 157 | } 158 | ], 159 | "stateMutability": "view", 160 | "type": "function" 161 | }, 162 | { 163 | "inputs": [], 164 | "name": "symbol", 165 | "outputs": [ 166 | { 167 | "internalType": "string", 168 | "name": "", 169 | "type": "string" 170 | } 171 | ], 172 | "stateMutability": "view", 173 | "type": "function" 174 | }, 175 | { 176 | "inputs": [], 177 | "name": "totalSupply", 178 | "outputs": [ 179 | { 180 | "internalType": "uint256", 181 | "name": "", 182 | "type": "uint256" 183 | } 184 | ], 185 | "stateMutability": "view", 186 | "type": "function" 187 | }, 188 | { 189 | "inputs": [ 190 | { 191 | "internalType": "address", 192 | "name": "to", 193 | "type": "address" 194 | }, 195 | { 196 | "internalType": "uint256", 197 | "name": "amount", 198 | "type": "uint256" 199 | } 200 | ], 201 | "name": "transfer", 202 | "outputs": [ 203 | { 204 | "internalType": "bool", 205 | "name": "", 206 | "type": "bool" 207 | } 208 | ], 209 | "stateMutability": "nonpayable", 210 | "type": "function" 211 | }, 212 | { 213 | "inputs": [ 214 | { 215 | "internalType": "address", 216 | "name": "from", 217 | "type": "address" 218 | }, 219 | { 220 | "internalType": "address", 221 | "name": "to", 222 | "type": "address" 223 | }, 224 | { 225 | "internalType": "uint256", 226 | "name": "amount", 227 | "type": "uint256" 228 | } 229 | ], 230 | "name": "transferFrom", 231 | "outputs": [ 232 | { 233 | "internalType": "bool", 234 | "name": "", 235 | "type": "bool" 236 | } 237 | ], 238 | "stateMutability": "nonpayable", 239 | "type": "function" 240 | } 241 | ], 242 | "bytecode": "0x", 243 | "deployedBytecode": "0x", 244 | "linkReferences": {}, 245 | "deployedLinkReferences": {} 246 | } 247 | -------------------------------------------------------------------------------- /artifacts/contracts/GIGADRIP20.sol/GIGADRIP20.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/3d4f9af651c29e8af0c3eea66a51781e.json" 4 | } 5 | -------------------------------------------------------------------------------- /artifacts/contracts/GIGADRIP20.sol/GIGADRIP20.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "GIGADRIP20", 4 | "sourceName": "contracts/GIGADRIP20.sol", 5 | "abi": [ 6 | { 7 | "anonymous": false, 8 | "inputs": [ 9 | { 10 | "indexed": true, 11 | "internalType": "address", 12 | "name": "owner", 13 | "type": "address" 14 | }, 15 | { 16 | "indexed": true, 17 | "internalType": "address", 18 | "name": "spender", 19 | "type": "address" 20 | }, 21 | { 22 | "indexed": false, 23 | "internalType": "uint256", 24 | "name": "amount", 25 | "type": "uint256" 26 | } 27 | ], 28 | "name": "Approval", 29 | "type": "event" 30 | }, 31 | { 32 | "anonymous": false, 33 | "inputs": [ 34 | { 35 | "indexed": true, 36 | "internalType": "address", 37 | "name": "from", 38 | "type": "address" 39 | }, 40 | { 41 | "indexed": true, 42 | "internalType": "address", 43 | "name": "to", 44 | "type": "address" 45 | }, 46 | { 47 | "indexed": false, 48 | "internalType": "uint256", 49 | "name": "amount", 50 | "type": "uint256" 51 | } 52 | ], 53 | "name": "Transfer", 54 | "type": "event" 55 | }, 56 | { 57 | "inputs": [ 58 | { 59 | "internalType": "address", 60 | "name": "", 61 | "type": "address" 62 | }, 63 | { 64 | "internalType": "address", 65 | "name": "", 66 | "type": "address" 67 | } 68 | ], 69 | "name": "allowance", 70 | "outputs": [ 71 | { 72 | "internalType": "uint256", 73 | "name": "", 74 | "type": "uint256" 75 | } 76 | ], 77 | "stateMutability": "view", 78 | "type": "function" 79 | }, 80 | { 81 | "inputs": [ 82 | { 83 | "internalType": "address", 84 | "name": "spender", 85 | "type": "address" 86 | }, 87 | { 88 | "internalType": "uint256", 89 | "name": "amount", 90 | "type": "uint256" 91 | } 92 | ], 93 | "name": "approve", 94 | "outputs": [ 95 | { 96 | "internalType": "bool", 97 | "name": "", 98 | "type": "bool" 99 | } 100 | ], 101 | "stateMutability": "nonpayable", 102 | "type": "function" 103 | }, 104 | { 105 | "inputs": [ 106 | { 107 | "internalType": "address", 108 | "name": "addr", 109 | "type": "address" 110 | } 111 | ], 112 | "name": "balanceOf", 113 | "outputs": [ 114 | { 115 | "internalType": "uint256", 116 | "name": "", 117 | "type": "uint256" 118 | } 119 | ], 120 | "stateMutability": "view", 121 | "type": "function" 122 | }, 123 | { 124 | "inputs": [], 125 | "name": "decimals", 126 | "outputs": [ 127 | { 128 | "internalType": "uint8", 129 | "name": "", 130 | "type": "uint8" 131 | } 132 | ], 133 | "stateMutability": "view", 134 | "type": "function" 135 | }, 136 | { 137 | "inputs": [], 138 | "name": "emissionRatePerBlock", 139 | "outputs": [ 140 | { 141 | "internalType": "uint256", 142 | "name": "", 143 | "type": "uint256" 144 | } 145 | ], 146 | "stateMutability": "view", 147 | "type": "function" 148 | }, 149 | { 150 | "inputs": [], 151 | "name": "name", 152 | "outputs": [ 153 | { 154 | "internalType": "string", 155 | "name": "", 156 | "type": "string" 157 | } 158 | ], 159 | "stateMutability": "view", 160 | "type": "function" 161 | }, 162 | { 163 | "inputs": [], 164 | "name": "symbol", 165 | "outputs": [ 166 | { 167 | "internalType": "string", 168 | "name": "", 169 | "type": "string" 170 | } 171 | ], 172 | "stateMutability": "view", 173 | "type": "function" 174 | }, 175 | { 176 | "inputs": [], 177 | "name": "totalSupply", 178 | "outputs": [ 179 | { 180 | "internalType": "uint256", 181 | "name": "", 182 | "type": "uint256" 183 | } 184 | ], 185 | "stateMutability": "view", 186 | "type": "function" 187 | }, 188 | { 189 | "inputs": [ 190 | { 191 | "internalType": "address", 192 | "name": "to", 193 | "type": "address" 194 | }, 195 | { 196 | "internalType": "uint256", 197 | "name": "amount", 198 | "type": "uint256" 199 | } 200 | ], 201 | "name": "transfer", 202 | "outputs": [ 203 | { 204 | "internalType": "bool", 205 | "name": "", 206 | "type": "bool" 207 | } 208 | ], 209 | "stateMutability": "nonpayable", 210 | "type": "function" 211 | }, 212 | { 213 | "inputs": [ 214 | { 215 | "internalType": "address", 216 | "name": "from", 217 | "type": "address" 218 | }, 219 | { 220 | "internalType": "address", 221 | "name": "to", 222 | "type": "address" 223 | }, 224 | { 225 | "internalType": "uint256", 226 | "name": "amount", 227 | "type": "uint256" 228 | } 229 | ], 230 | "name": "transferFrom", 231 | "outputs": [ 232 | { 233 | "internalType": "bool", 234 | "name": "", 235 | "type": "bool" 236 | } 237 | ], 238 | "stateMutability": "nonpayable", 239 | "type": "function" 240 | } 241 | ], 242 | "bytecode": "0x", 243 | "deployedBytecode": "0x", 244 | "linkReferences": {}, 245 | "deployedLinkReferences": {} 246 | } 247 | -------------------------------------------------------------------------------- /artifacts/contracts/test20.sol/Test20.dbg.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-dbg-1", 3 | "buildInfo": "../../build-info/96e5e6b1354355ca45a0252c1a4cdda9.json" 4 | } 5 | -------------------------------------------------------------------------------- /artifacts/contracts/test20.sol/Test20.json: -------------------------------------------------------------------------------- 1 | { 2 | "_format": "hh-sol-artifact-1", 3 | "contractName": "Test20", 4 | "sourceName": "contracts/test20.sol", 5 | "abi": [ 6 | { 7 | "inputs": [ 8 | { 9 | "internalType": "string", 10 | "name": "_name", 11 | "type": "string" 12 | }, 13 | { 14 | "internalType": "string", 15 | "name": "_symbol", 16 | "type": "string" 17 | }, 18 | { 19 | "internalType": "uint8", 20 | "name": "_decimals", 21 | "type": "uint8" 22 | }, 23 | { 24 | "internalType": "uint256", 25 | "name": "_emissionRatePerBlock", 26 | "type": "uint256" 27 | } 28 | ], 29 | "stateMutability": "nonpayable", 30 | "type": "constructor" 31 | }, 32 | { 33 | "anonymous": false, 34 | "inputs": [ 35 | { 36 | "indexed": true, 37 | "internalType": "address", 38 | "name": "owner", 39 | "type": "address" 40 | }, 41 | { 42 | "indexed": true, 43 | "internalType": "address", 44 | "name": "spender", 45 | "type": "address" 46 | }, 47 | { 48 | "indexed": false, 49 | "internalType": "uint256", 50 | "name": "amount", 51 | "type": "uint256" 52 | } 53 | ], 54 | "name": "Approval", 55 | "type": "event" 56 | }, 57 | { 58 | "anonymous": false, 59 | "inputs": [ 60 | { 61 | "indexed": true, 62 | "internalType": "address", 63 | "name": "from", 64 | "type": "address" 65 | }, 66 | { 67 | "indexed": true, 68 | "internalType": "address", 69 | "name": "to", 70 | "type": "address" 71 | }, 72 | { 73 | "indexed": false, 74 | "internalType": "uint256", 75 | "name": "amount", 76 | "type": "uint256" 77 | } 78 | ], 79 | "name": "Transfer", 80 | "type": "event" 81 | }, 82 | { 83 | "inputs": [ 84 | { 85 | "internalType": "address", 86 | "name": "", 87 | "type": "address" 88 | }, 89 | { 90 | "internalType": "address", 91 | "name": "", 92 | "type": "address" 93 | } 94 | ], 95 | "name": "allowance", 96 | "outputs": [ 97 | { 98 | "internalType": "uint256", 99 | "name": "", 100 | "type": "uint256" 101 | } 102 | ], 103 | "stateMutability": "view", 104 | "type": "function" 105 | }, 106 | { 107 | "inputs": [ 108 | { 109 | "internalType": "address", 110 | "name": "spender", 111 | "type": "address" 112 | }, 113 | { 114 | "internalType": "uint256", 115 | "name": "amount", 116 | "type": "uint256" 117 | } 118 | ], 119 | "name": "approve", 120 | "outputs": [ 121 | { 122 | "internalType": "bool", 123 | "name": "", 124 | "type": "bool" 125 | } 126 | ], 127 | "stateMutability": "nonpayable", 128 | "type": "function" 129 | }, 130 | { 131 | "inputs": [ 132 | { 133 | "internalType": "address", 134 | "name": "addr", 135 | "type": "address" 136 | } 137 | ], 138 | "name": "balanceOf", 139 | "outputs": [ 140 | { 141 | "internalType": "uint256", 142 | "name": "", 143 | "type": "uint256" 144 | } 145 | ], 146 | "stateMutability": "view", 147 | "type": "function" 148 | }, 149 | { 150 | "inputs": [ 151 | { 152 | "internalType": "address", 153 | "name": "from", 154 | "type": "address" 155 | }, 156 | { 157 | "internalType": "uint256", 158 | "name": "value", 159 | "type": "uint256" 160 | } 161 | ], 162 | "name": "burn", 163 | "outputs": [], 164 | "stateMutability": "nonpayable", 165 | "type": "function" 166 | }, 167 | { 168 | "inputs": [], 169 | "name": "decimals", 170 | "outputs": [ 171 | { 172 | "internalType": "uint8", 173 | "name": "", 174 | "type": "uint8" 175 | } 176 | ], 177 | "stateMutability": "view", 178 | "type": "function" 179 | }, 180 | { 181 | "inputs": [], 182 | "name": "emissionRatePerBlock", 183 | "outputs": [ 184 | { 185 | "internalType": "uint256", 186 | "name": "", 187 | "type": "uint256" 188 | } 189 | ], 190 | "stateMutability": "view", 191 | "type": "function" 192 | }, 193 | { 194 | "inputs": [ 195 | { 196 | "internalType": "uint256", 197 | "name": "amount", 198 | "type": "uint256" 199 | } 200 | ], 201 | "name": "mint", 202 | "outputs": [], 203 | "stateMutability": "nonpayable", 204 | "type": "function" 205 | }, 206 | { 207 | "inputs": [], 208 | "name": "name", 209 | "outputs": [ 210 | { 211 | "internalType": "string", 212 | "name": "", 213 | "type": "string" 214 | } 215 | ], 216 | "stateMutability": "view", 217 | "type": "function" 218 | }, 219 | { 220 | "inputs": [ 221 | { 222 | "internalType": "address", 223 | "name": "addr", 224 | "type": "address" 225 | } 226 | ], 227 | "name": "startDripping", 228 | "outputs": [], 229 | "stateMutability": "nonpayable", 230 | "type": "function" 231 | }, 232 | { 233 | "inputs": [ 234 | { 235 | "internalType": "address", 236 | "name": "addr", 237 | "type": "address" 238 | } 239 | ], 240 | "name": "stopDripping", 241 | "outputs": [], 242 | "stateMutability": "nonpayable", 243 | "type": "function" 244 | }, 245 | { 246 | "inputs": [], 247 | "name": "symbol", 248 | "outputs": [ 249 | { 250 | "internalType": "string", 251 | "name": "", 252 | "type": "string" 253 | } 254 | ], 255 | "stateMutability": "view", 256 | "type": "function" 257 | }, 258 | { 259 | "inputs": [], 260 | "name": "totalSupply", 261 | "outputs": [ 262 | { 263 | "internalType": "uint256", 264 | "name": "", 265 | "type": "uint256" 266 | } 267 | ], 268 | "stateMutability": "view", 269 | "type": "function" 270 | }, 271 | { 272 | "inputs": [ 273 | { 274 | "internalType": "address", 275 | "name": "to", 276 | "type": "address" 277 | }, 278 | { 279 | "internalType": "uint256", 280 | "name": "amount", 281 | "type": "uint256" 282 | } 283 | ], 284 | "name": "transfer", 285 | "outputs": [ 286 | { 287 | "internalType": "bool", 288 | "name": "", 289 | "type": "bool" 290 | } 291 | ], 292 | "stateMutability": "nonpayable", 293 | "type": "function" 294 | }, 295 | { 296 | "inputs": [ 297 | { 298 | "internalType": "address", 299 | "name": "from", 300 | "type": "address" 301 | }, 302 | { 303 | "internalType": "address", 304 | "name": "to", 305 | "type": "address" 306 | }, 307 | { 308 | "internalType": "uint256", 309 | "name": "amount", 310 | "type": "uint256" 311 | } 312 | ], 313 | "name": "transferFrom", 314 | "outputs": [ 315 | { 316 | "internalType": "bool", 317 | "name": "", 318 | "type": "bool" 319 | } 320 | ], 321 | "stateMutability": "nonpayable", 322 | "type": "function" 323 | } 324 | ], 325 | "bytecode": "0x60c06040523480156200001157600080fd5b5060405162000d8f38038062000d8f8339810160408190526200003491620001f2565b838383838360009080519060200190620000509291906200007f565b508251620000669060019060208601906200007f565b5060ff90911660805260a05250620002b9945050505050565b8280546200008d906200027d565b90600052602060002090601f016020900481019282620000b15760008555620000fc565b82601f10620000cc57805160ff1916838001178555620000fc565b82800160010185558215620000fc579182015b82811115620000fc578251825591602001919060010190620000df565b506200010a9291506200010e565b5090565b5b808211156200010a57600081556001016200010f565b634e487b7160e01b600052604160045260246000fd5b600082601f8301126200014d57600080fd5b81516001600160401b03808211156200016a576200016a62000125565b604051601f8301601f19908116603f0116810190828211818310171562000195576200019562000125565b81604052838152602092508683858801011115620001b257600080fd5b600091505b83821015620001d65785820183015181830184015290820190620001b7565b83821115620001e85760008385830101525b9695505050505050565b600080600080608085870312156200020957600080fd5b84516001600160401b03808211156200022157600080fd5b6200022f888389016200013b565b955060208701519150808211156200024657600080fd5b5062000255878288016200013b565b935050604085015160ff811681146200026d57600080fd5b6060959095015193969295505050565b600181811c908216806200029257607f821691505b602082108103620002b357634e487b7160e01b600052602260045260246000fd5b50919050565b60805160a051610aa2620002ed600039600081816101bf0152818161036101526104670152600061015e0152610aa26000f3fe608060405234801561001057600080fd5b50600436106100ea5760003560e01c80638b5298391161008c578063a0712d6811610066578063a0712d68146101fc578063a9059cbb1461020f578063b5484c5614610222578063dd62ed3e1461023557600080fd5b80638b529839146101ba57806395d89b41146101e15780639dc29fac146101e957600080fd5b806323b872dd116100c857806323b872dd14610146578063313ce5671461015957806340ea8d881461019257806370a08231146101a757600080fd5b806306fdde03146100ef578063095ea7b31461010d57806318160ddd14610130575b600080fd5b6100f7610260565b6040516101049190610872565b60405180910390f35b61012061011b3660046108e3565b6102ee565b6040519015158152602001610104565b61013861035a565b604051908152602001610104565b61012061015436600461090d565b6103b4565b6101807f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff9091168152602001610104565b6101a56101a0366004610949565b610426565b005b6101386101b5366004610949565b610432565b6101387f000000000000000000000000000000000000000000000000000000000000000081565b6100f76104ac565b6101a56101f73660046108e3565b6104b9565b6101a561020a36600461096b565b6104c7565b61012061021d3660046108e3565b6104d1565b6101a5610230366004610949565b6104e7565b610138610243366004610984565b600260209081526000928352604080842090915290825290205481565b6000805461026d906109b7565b80601f0160208091040260200160405190810160405280929190818152602001828054610299906109b7565b80156102e65780601f106102bb576101008083540402835291602001916102e6565b820191906000526020600020905b8154815290600101906020018083116102c957829003601f168201915b505050505081565b3360008181526002602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103499086815260200190565b60405180910390a350600192915050565b60006007547f00000000000000000000000000000000000000000000000000000000000000006006544361038e9190610a07565b6103989190610a1e565b6103a29190610a1e565b6005546103af9190610a3d565b905090565b6001600160a01b03831660009081526002602090815260408083203384529091528120546000198114610410576103eb8382610a07565b6001600160a01b03861660009081526002602090815260408083203384529091529020555b61041b8585856104f0565b506001949350505050565b61042f81610616565b50565b6001600160a01b0381166000908152600460209081526040808320546003909252822054818303610464579392505050565b807f00000000000000000000000000000000000000000000000000000000000000006104908443610a07565b61049a9190610a1e565b6104a49190610a3d565b949350505050565b6001805461026d906109b7565b6104c382826106aa565b5050565b61042f3382610762565b60006104de3384846104f0565b50600192915050565b61042f816107b7565b6001600160a01b0382166105575760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084015b60405180910390fd5b8061056184610432565b61056b9190610a07565b6001600160a01b03808516600081815260036020908152604080832095909555928616815283812080548601905590815260049091522054156105c4576001600160a01b03831660009081526004602052604090204390555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161060991815260200190565b60405180910390a3505050565b6001600160a01b038116600090815260046020526040902054156106745760405162461bcd60e51b81526020600482015260156024820152747573657220616c7265616479206163637275696e6760581b604482015260640161054e565b61067c61035a565b6005554360068190556007805460010190556001600160a01b03909116600090815260046020526040902055565b6106b261035a565b60055543600655806106c383610432565b6106cd9190610a07565b6001600160a01b0383166000908152600360209081526040808320939093556005805485900390556004905220541561071c576001600160a01b03821660009081526004602052604090204390555b6040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906020015b60405180910390a35050565b60058054820190556001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610756565b6001600160a01b03811660009081526004602052604081205490036108125760405162461bcd60e51b815260206004820152601160248201527075736572206e6f74206163637275696e6760781b604482015260640161054e565b61081b81610432565b6001600160a01b03821660009081526003602052604090205561083c61035a565b600555436006556007805490600061085383610a55565b90915550506001600160a01b0316600090815260046020526040812055565b600060208083528351808285015260005b8181101561089f57858101830151858201604001528201610883565b818111156108b1576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108de57600080fd5b919050565b600080604083850312156108f657600080fd5b6108ff836108c7565b946020939093013593505050565b60008060006060848603121561092257600080fd5b61092b846108c7565b9250610939602085016108c7565b9150604084013590509250925092565b60006020828403121561095b57600080fd5b610964826108c7565b9392505050565b60006020828403121561097d57600080fd5b5035919050565b6000806040838503121561099757600080fd5b6109a0836108c7565b91506109ae602084016108c7565b90509250929050565b600181811c908216806109cb57607f821691505b6020821081036109eb57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a1957610a196109f1565b500390565b6000816000190483118215151615610a3857610a386109f1565b500290565b60008219821115610a5057610a506109f1565b500190565b600081610a6457610a646109f1565b50600019019056fea2646970667358221220e426fe8ac7e067e7b8ac3dfef0029a45c9d4844c99c82179c32312ea9b1e5bc864736f6c634300080d0033", 326 | "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100ea5760003560e01c80638b5298391161008c578063a0712d6811610066578063a0712d68146101fc578063a9059cbb1461020f578063b5484c5614610222578063dd62ed3e1461023557600080fd5b80638b529839146101ba57806395d89b41146101e15780639dc29fac146101e957600080fd5b806323b872dd116100c857806323b872dd14610146578063313ce5671461015957806340ea8d881461019257806370a08231146101a757600080fd5b806306fdde03146100ef578063095ea7b31461010d57806318160ddd14610130575b600080fd5b6100f7610260565b6040516101049190610872565b60405180910390f35b61012061011b3660046108e3565b6102ee565b6040519015158152602001610104565b61013861035a565b604051908152602001610104565b61012061015436600461090d565b6103b4565b6101807f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff9091168152602001610104565b6101a56101a0366004610949565b610426565b005b6101386101b5366004610949565b610432565b6101387f000000000000000000000000000000000000000000000000000000000000000081565b6100f76104ac565b6101a56101f73660046108e3565b6104b9565b6101a561020a36600461096b565b6104c7565b61012061021d3660046108e3565b6104d1565b6101a5610230366004610949565b6104e7565b610138610243366004610984565b600260209081526000928352604080842090915290825290205481565b6000805461026d906109b7565b80601f0160208091040260200160405190810160405280929190818152602001828054610299906109b7565b80156102e65780601f106102bb576101008083540402835291602001916102e6565b820191906000526020600020905b8154815290600101906020018083116102c957829003601f168201915b505050505081565b3360008181526002602090815260408083206001600160a01b038716808552925280832085905551919290917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925906103499086815260200190565b60405180910390a350600192915050565b60006007547f00000000000000000000000000000000000000000000000000000000000000006006544361038e9190610a07565b6103989190610a1e565b6103a29190610a1e565b6005546103af9190610a3d565b905090565b6001600160a01b03831660009081526002602090815260408083203384529091528120546000198114610410576103eb8382610a07565b6001600160a01b03861660009081526002602090815260408083203384529091529020555b61041b8585856104f0565b506001949350505050565b61042f81610616565b50565b6001600160a01b0381166000908152600460209081526040808320546003909252822054818303610464579392505050565b807f00000000000000000000000000000000000000000000000000000000000000006104908443610a07565b61049a9190610a1e565b6104a49190610a3d565b949350505050565b6001805461026d906109b7565b6104c382826106aa565b5050565b61042f3382610762565b60006104de3384846104f0565b50600192915050565b61042f816107b7565b6001600160a01b0382166105575760405162461bcd60e51b815260206004820152602360248201527f45524332303a207472616e7366657220746f20746865207a65726f206164647260448201526265737360e81b60648201526084015b60405180910390fd5b8061056184610432565b61056b9190610a07565b6001600160a01b03808516600081815260036020908152604080832095909555928616815283812080548601905590815260049091522054156105c4576001600160a01b03831660009081526004602052604090204390555b816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8360405161060991815260200190565b60405180910390a3505050565b6001600160a01b038116600090815260046020526040902054156106745760405162461bcd60e51b81526020600482015260156024820152747573657220616c7265616479206163637275696e6760581b604482015260640161054e565b61067c61035a565b6005554360068190556007805460010190556001600160a01b03909116600090815260046020526040902055565b6106b261035a565b60055543600655806106c383610432565b6106cd9190610a07565b6001600160a01b0383166000908152600360209081526040808320939093556005805485900390556004905220541561071c576001600160a01b03821660009081526004602052604090204390555b6040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906020015b60405180910390a35050565b60058054820190556001600160a01b0382166000818152600360209081526040808320805486019055518481527fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9101610756565b6001600160a01b03811660009081526004602052604081205490036108125760405162461bcd60e51b815260206004820152601160248201527075736572206e6f74206163637275696e6760781b604482015260640161054e565b61081b81610432565b6001600160a01b03821660009081526003602052604090205561083c61035a565b600555436006556007805490600061085383610a55565b90915550506001600160a01b0316600090815260046020526040812055565b600060208083528351808285015260005b8181101561089f57858101830151858201604001528201610883565b818111156108b1576000604083870101525b50601f01601f1916929092016040019392505050565b80356001600160a01b03811681146108de57600080fd5b919050565b600080604083850312156108f657600080fd5b6108ff836108c7565b946020939093013593505050565b60008060006060848603121561092257600080fd5b61092b846108c7565b9250610939602085016108c7565b9150604084013590509250925092565b60006020828403121561095b57600080fd5b610964826108c7565b9392505050565b60006020828403121561097d57600080fd5b5035919050565b6000806040838503121561099757600080fd5b6109a0836108c7565b91506109ae602084016108c7565b90509250929050565b600181811c908216806109cb57607f821691505b6020821081036109eb57634e487b7160e01b600052602260045260246000fd5b50919050565b634e487b7160e01b600052601160045260246000fd5b600082821015610a1957610a196109f1565b500390565b6000816000190483118215151615610a3857610a386109f1565b500290565b60008219821115610a5057610a506109f1565b500190565b600081610a6457610a646109f1565b50600019019056fea2646970667358221220e426fe8ac7e067e7b8ac3dfef0029a45c9d4844c99c82179c32312ea9b1e5bc864736f6c634300080d0033", 327 | "linkReferences": {}, 328 | "deployedLinkReferences": {} 329 | } 330 | -------------------------------------------------------------------------------- /foundry.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | src = 'src' 3 | out = 'out' 4 | libs = ['lib'] 5 | 6 | # See more config options https://github.com/gakonst/foundry/tree/master/config -------------------------------------------------------------------------------- /gas-report.txt: -------------------------------------------------------------------------------- 1 | No files changed, compilation skipped 2 | 3 | Running 14 tests for test/DRIP20.t.sol:DRIP20Test 4 | [PASS] testBurnFromDrippingUser() (gas: 101718) 5 | [PASS] testDripMultiUser() (gas: 183686) 6 | [PASS] testDripSingleUser() (gas: 95210) 7 | [PASS] testMint() (gas: 67709) 8 | [PASS] testRevertStartDrip() (gas: 85816) 9 | [PASS] testRevertStopDrip() (gas: 15817) 10 | [PASS] testSimpleBurn() (gas: 95507) 11 | [PASS] testSimpleTransfer() (gas: 99330) 12 | [PASS] testSimpleTransferFrom() (gas: 125318) 13 | [PASS] testStopDripMultiUser() (gas: 193009) 14 | [PASS] testStopDripSingleUser() (gas: 107080) 15 | [PASS] testTransferFromDrippingUserToDrippingUser() (gas: 176876) 16 | [PASS] testTransferFromDrippingUserToNonDrippingUser() (gas: 150414) 17 | [PASS] testTransferFromNonDrippingUserToDrippingUser() (gas: 175058) 18 | Test result: ok. 14 passed; 0 failed; finished in 6.38ms 19 | 20 | Running 22 tests for test/GIGADRIP20.t.sol:GIGADRIP20Test 21 | [PASS] testBurnFromDrippingUser() (gas: 81507) 22 | [PASS] testBurnFromDrippingUserMultiplier() (gas: 121261) 23 | [PASS] testBurnFromNonDrippingUser() (gas: 105288) 24 | [PASS] testMint() (gas: 65795) 25 | [PASS] testSimpleBurn() (gas: 93817) 26 | [PASS] testSimpleTransfer() (gas: 97519) 27 | [PASS] testSimpleTransferFrom() (gas: 123582) 28 | [PASS] testStopStreamMultiUser() (gas: 177852) 29 | [PASS] testStopStreamMultiUserMultiplier() (gas: 198504) 30 | [PASS] testStopStreamRevert() (gas: 69036) 31 | [PASS] testStopStreamSingleUser() (gas: 99929) 32 | [PASS] testStreamMultiUser() (gas: 165451) 33 | [PASS] testStreamMultiUserMultiplier() (gas: 165460) 34 | [PASS] testStreamSingUserMultiDrip() (gas: 181251) 35 | [PASS] testStreamSingleUser() (gas: 71847) 36 | [PASS] testStreamSingleUserMultiplier() (gas: 71935) 37 | [PASS] testTransferFromDrippingUserToDrippingUser() (gas: 158033) 38 | [PASS] testTransferFromDrippingUserToDrippingUserMultiplier() (gas: 157934) 39 | [PASS] testTransferFromDrippingUserToNonDrippingUser() (gas: 130299) 40 | [PASS] testTransferFromDrippingUserToNonDrippingUserMultiplier() (gas: 130201) 41 | [PASS] testTransferFromNonDrippingUserToDrippingUser() (gas: 154817) 42 | [PASS] testTransferFromNonDrippingUserToDrippingUserMultiplier() (gas: 154739) 43 | Test result: ok. 22 passed; 0 failed; finished in 6.38ms 44 | ╭───────────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ 45 | │ test/mocks/MockDRIP20.sol:MockDRIP20 contract ┆ ┆ ┆ ┆ ┆ │ 46 | ╞═══════════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ 47 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 48 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 49 | │ 596157 ┆ 3751 ┆ ┆ ┆ ┆ │ 50 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 51 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 52 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 53 | │ approve ┆ 24545 ┆ 24545 ┆ 24545 ┆ 24545 ┆ 1 │ 54 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 55 | │ balanceOf ┆ 838 ┆ 1777 ┆ 1075 ┆ 3075 ┆ 48 │ 56 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 57 | │ burn ┆ 19672 ┆ 21915 ┆ 21915 ┆ 24158 ┆ 2 │ 58 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 59 | │ mint ┆ 44450 ┆ 46050 ┆ 46450 ┆ 46450 ┆ 5 │ 60 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 61 | │ startDripping ┆ 707 ┆ 52817 ┆ 71475 ┆ 71475 ┆ 15 │ 62 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 63 | │ stopDripping ┆ 2709 ┆ 22257 ┆ 26120 ┆ 34080 ┆ 4 │ 64 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 65 | │ totalSupply ┆ 915 ┆ 1486 ┆ 915 ┆ 4915 ┆ 28 │ 66 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 67 | │ transfer ┆ 23465 ┆ 35131 ┆ 35631 ┆ 45797 ┆ 4 │ 68 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 69 | │ transferFrom ┆ 27834 ┆ 27834 ┆ 27834 ┆ 27834 ┆ 1 │ 70 | ╰───────────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ 71 | ╭───────────────────────────────────────────────────────┬─────────────────┬───────┬────────┬───────┬─────────╮ 72 | │ test/mocks/MockGIGADRIP20.sol:MockGIGADRIP20 contract ┆ ┆ ┆ ┆ ┆ │ 73 | ╞═══════════════════════════════════════════════════════╪═════════════════╪═══════╪════════╪═══════╪═════════╡ 74 | │ Deployment Cost ┆ Deployment Size ┆ ┆ ┆ ┆ │ 75 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 76 | │ 685113 ┆ 4229 ┆ ┆ ┆ ┆ │ 77 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 78 | │ Function Name ┆ min ┆ avg ┆ median ┆ max ┆ # calls │ 79 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 80 | │ approve ┆ 24545 ┆ 24545 ┆ 24545 ┆ 24545 ┆ 1 │ 81 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 82 | │ balanceOf ┆ 919 ┆ 1931 ┆ 1317 ┆ 3317 ┆ 99 │ 83 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 84 | │ burn ┆ 19436 ┆ 27135 ┆ 22127 ┆ 44851 ┆ 4 │ 85 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 86 | │ mint ┆ 44467 ┆ 45800 ┆ 46467 ┆ 46467 ┆ 6 │ 87 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 88 | │ startDripping ┆ 3506 ┆ 40210 ┆ 50161 ┆ 50161 ┆ 32 │ 89 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 90 | │ stopDripping ┆ 4857 ┆ 26404 ┆ 27352 ┆ 44164 ┆ 6 │ 91 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 92 | │ totalSupply ┆ 859 ┆ 1007 ┆ 859 ┆ 2859 ┆ 54 │ 93 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 94 | │ transfer ┆ 23499 ┆ 35359 ┆ 36106 ┆ 46056 ┆ 8 │ 95 | ├╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌┼╌╌╌╌╌╌╌╌╌┤ 96 | │ transferFrom ┆ 27868 ┆ 27868 ┆ 27868 ┆ 27868 ┆ 1 │ 97 | ╰───────────────────────────────────────────────────────┴─────────────────┴───────┴────────┴───────┴─────────╯ 98 | 99 | -------------------------------------------------------------------------------- /src/DRIP20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | ///@author 0xBeans 5 | ///@notice This is an ERC20 implementation that supports constant token emissions (drips) to wallets. 6 | ///@notice This allows tokens to be streamed/dripped to users per block via an emission rate without users 7 | ///@notice ever having to send a separate transaction to 'claim' tokens. 8 | ///@notice shout out to solmate (@t11s) for the slim and efficient ERC20 implementation! 9 | ///@notice shout out to superfluid and UBI for the dripping inspiration! 10 | abstract contract DRIP20 { 11 | /*============================================================== 12 | == ERRORS == 13 | ==============================================================*/ 14 | error UserNotAccruing(); 15 | error UserAlreadyAccruing(); 16 | error ERC20_TransferToZeroAddress(); 17 | 18 | /*============================================================== 19 | == EVENTS == 20 | ==============================================================*/ 21 | 22 | event Transfer(address indexed from, address indexed to, uint256 amount); 23 | 24 | event Approval( 25 | address indexed owner, 26 | address indexed spender, 27 | uint256 amount 28 | ); 29 | 30 | /*============================================================== 31 | == METADATA STORAGE == 32 | ==============================================================*/ 33 | 34 | string public name; 35 | 36 | string public symbol; 37 | 38 | uint8 public immutable decimals; 39 | 40 | /*============================================================== 41 | == ERC20 STORAGE == 42 | ==============================================================*/ 43 | 44 | mapping(address => mapping(address => uint256)) public allowance; 45 | 46 | /*============================================================== 47 | == DRIP STORAGE == 48 | ==============================================================*/ 49 | 50 | // immutable token emission rate per block 51 | uint256 public immutable emissionRatePerBlock; 52 | 53 | mapping(address => uint256) private _balance; 54 | 55 | mapping(address => uint256) private _accrualStartBlock; 56 | 57 | // these are all used for calculating totalSupply() 58 | uint256 private _currAccrued; 59 | uint256 private _currEmissionBlockNum; 60 | uint256 private _currNumAccruers; 61 | 62 | constructor( 63 | string memory _name, 64 | string memory _symbol, 65 | uint8 _decimals, 66 | uint256 _emissionRatePerBlock 67 | ) { 68 | name = _name; 69 | symbol = _symbol; 70 | decimals = _decimals; 71 | emissionRatePerBlock = _emissionRatePerBlock; 72 | } 73 | 74 | /*============================================================== 75 | == ERC20 IMPL == 76 | ==============================================================*/ 77 | 78 | function approve(address spender, uint256 amount) 79 | public 80 | virtual 81 | returns (bool) 82 | { 83 | allowance[msg.sender][spender] = amount; 84 | 85 | emit Approval(msg.sender, spender, amount); 86 | 87 | return true; 88 | } 89 | 90 | function _transfer( 91 | address from, 92 | address to, 93 | uint256 amount 94 | ) internal virtual { 95 | if (to == address(0)) revert ERC20_TransferToZeroAddress(); 96 | 97 | _balance[from] = balanceOf(from) - amount; 98 | 99 | unchecked { 100 | _balance[to] += amount; 101 | } 102 | 103 | if (_accrualStartBlock[from] != 0) { 104 | _accrualStartBlock[from] = block.number; 105 | } 106 | 107 | emit Transfer(from, to, amount); 108 | } 109 | 110 | function transfer(address to, uint256 amount) 111 | public 112 | virtual 113 | returns (bool) 114 | { 115 | _transfer(msg.sender, to, amount); 116 | return true; 117 | } 118 | 119 | function transferFrom( 120 | address from, 121 | address to, 122 | uint256 amount 123 | ) public virtual returns (bool) { 124 | uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. 125 | 126 | if (allowed != type(uint256).max) 127 | allowance[from][msg.sender] = allowed - amount; 128 | 129 | _transfer(from, to, amount); 130 | 131 | return true; 132 | } 133 | 134 | function balanceOf(address addr) public view returns (uint256) { 135 | uint256 addrAccrualStartBlock = _accrualStartBlock[addr]; 136 | uint256 accruedBalance = _balance[addr]; 137 | 138 | if (addrAccrualStartBlock == 0) { 139 | return accruedBalance; 140 | } 141 | 142 | return 143 | ((block.number - addrAccrualStartBlock) * emissionRatePerBlock) + 144 | accruedBalance; 145 | } 146 | 147 | function totalSupply() public view returns (uint256) { 148 | return 149 | _currAccrued + 150 | (block.number - _currEmissionBlockNum) * 151 | emissionRatePerBlock * 152 | _currNumAccruers; 153 | } 154 | 155 | /*============================================================== 156 | == DRIP LOGIC == 157 | ==============================================================*/ 158 | 159 | /** 160 | * @dev Add an address to start dripping tokens to. 161 | * @dev We need to update _currAccrued whenever we add a new dripper to properly update totalSupply() 162 | * @dev IMPORTANT: Can only call this on an address thats not accruing 163 | * @param addr address to drip to 164 | */ 165 | function _startDripping(address addr) internal virtual { 166 | if (_accrualStartBlock[addr] != 0) revert UserAlreadyAccruing(); 167 | 168 | _currAccrued = totalSupply(); 169 | _currEmissionBlockNum = block.number; 170 | 171 | unchecked { 172 | _currNumAccruers++; 173 | } 174 | 175 | _accrualStartBlock[addr] = block.number; 176 | 177 | // emit Transfer event when new address starts dripping 178 | emit Transfer(address(0), addr, 0); 179 | } 180 | 181 | /** 182 | * @dev Add an address to stop dripping tokens to. 183 | * @dev We need to update _currAccrued whenever we remove a dripper to properly update totalSupply() 184 | * @dev IMPORTANT: Can only call this on an address that is accruing 185 | * @param addr address to stop dripping to 186 | */ 187 | function _stopDripping(address addr) internal virtual { 188 | if (_accrualStartBlock[addr] == 0) revert UserNotAccruing(); 189 | 190 | _balance[addr] = balanceOf(addr); 191 | _currAccrued = totalSupply(); 192 | _currEmissionBlockNum = block.number; 193 | _currNumAccruers--; 194 | _accrualStartBlock[addr] = 0; 195 | } 196 | 197 | /*============================================================== 198 | == MINT/BURN == 199 | ==============================================================*/ 200 | 201 | function _mint(address to, uint256 amount) internal virtual { 202 | unchecked { 203 | _currAccrued += amount; 204 | _balance[to] += amount; 205 | } 206 | 207 | emit Transfer(address(0), to, amount); 208 | } 209 | 210 | function _burn(address from, uint256 amount) internal virtual { 211 | // have to update supply before burning 212 | _currAccrued = totalSupply(); 213 | _currEmissionBlockNum = block.number; 214 | _balance[from] = balanceOf(from) - amount; 215 | 216 | // Cannot underflow because amount can 217 | // never be greater than the totalSupply() 218 | unchecked { 219 | _currAccrued -= amount; 220 | } 221 | 222 | if (_accrualStartBlock[from] != 0) { 223 | _accrualStartBlock[from] = block.number; 224 | } 225 | 226 | emit Transfer(from, address(0), amount); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/GIGADRIP20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.0; 3 | 4 | ///@author 0xBeans 5 | ///@notice This is a beefed up ERC20 implementation of DRIP20 that supports emission multipliers. 6 | ///@notice Multipliers are useful when certain users should accrue larger emissions. For example, 7 | ///@notice if an NFT drips 10 tokens per block to a user, and the user has 3 NFTs, then the user 8 | ///@notice should accrue 3 times as many tokens per block. This user would have a multiplier of 3. 9 | ///@notice shout out to solmate (@t11s) for the slim and efficient ERC20 implementation! 10 | ///@notice shout out to superfluid and UBI for the dripping inspiration! 11 | abstract contract GIGADRIP20 { 12 | /*============================================================== 13 | == ERRORS == 14 | ==============================================================*/ 15 | 16 | error UserNotAccruing(); 17 | error ERC20_TransferToZeroAddress(); 18 | 19 | /*============================================================== 20 | == EVENTS == 21 | ==============================================================*/ 22 | 23 | event Transfer(address indexed from, address indexed to, uint256 amount); 24 | 25 | event Approval( 26 | address indexed owner, 27 | address indexed spender, 28 | uint256 amount 29 | ); 30 | 31 | /*============================================================== 32 | == METADATA STORAGE == 33 | ==============================================================*/ 34 | 35 | string public name; 36 | 37 | string public symbol; 38 | 39 | uint8 public immutable decimals; 40 | 41 | /*============================================================== 42 | == ERC20 STORAGE == 43 | ==============================================================*/ 44 | 45 | mapping(address => mapping(address => uint256)) public allowance; 46 | 47 | /*============================================================== 48 | == DRIP STORAGE == 49 | ==============================================================*/ 50 | 51 | struct Accruer { 52 | uint256 balance; 53 | uint128 accrualStartBlock; 54 | uint128 multiplier; 55 | } 56 | 57 | // immutable token emission rate per block 58 | uint256 public immutable emissionRatePerBlock; 59 | 60 | // wallets currently getting dripped tokens 61 | mapping(address => Accruer) private _accruers; 62 | 63 | // these are all for calculating totalSupply() 64 | uint256 private _currAccrued; 65 | uint128 private _currEmissionBlockNum; 66 | uint128 private _currEmissionMultiple; 67 | 68 | constructor( 69 | string memory _name, 70 | string memory _symbol, 71 | uint8 _decimals, 72 | uint256 _emissionRatePerBlock 73 | ) { 74 | name = _name; 75 | symbol = _symbol; 76 | decimals = _decimals; 77 | emissionRatePerBlock = _emissionRatePerBlock; 78 | } 79 | 80 | /*============================================================== 81 | == ERC20 IMPL == 82 | ==============================================================*/ 83 | 84 | function approve(address spender, uint256 amount) 85 | public 86 | virtual 87 | returns (bool) 88 | { 89 | allowance[msg.sender][spender] = amount; 90 | 91 | emit Approval(msg.sender, spender, amount); 92 | 93 | return true; 94 | } 95 | 96 | function transfer(address to, uint256 amount) 97 | public 98 | virtual 99 | returns (bool) 100 | { 101 | _transfer(msg.sender, to, amount); 102 | return true; 103 | } 104 | 105 | function transferFrom( 106 | address from, 107 | address to, 108 | uint256 amount 109 | ) public virtual returns (bool) { 110 | uint256 allowed = allowance[from][msg.sender]; 111 | 112 | if (allowed != type(uint256).max) 113 | allowance[from][msg.sender] = allowed - amount; 114 | 115 | _transfer(from, to, amount); 116 | 117 | return true; 118 | } 119 | 120 | function balanceOf(address addr) public view returns (uint256) { 121 | Accruer memory accruer = _accruers[addr]; 122 | 123 | if (accruer.accrualStartBlock == 0) { 124 | return accruer.balance; 125 | } 126 | 127 | return 128 | ((block.number - accruer.accrualStartBlock) * 129 | emissionRatePerBlock) * 130 | accruer.multiplier + 131 | accruer.balance; 132 | } 133 | 134 | function totalSupply() public view returns (uint256) { 135 | return 136 | _currAccrued + 137 | (block.number - _currEmissionBlockNum) * 138 | emissionRatePerBlock * 139 | _currEmissionMultiple; 140 | } 141 | 142 | function _transfer( 143 | address from, 144 | address to, 145 | uint256 amount 146 | ) internal virtual { 147 | if (to == address(0)) revert ERC20_TransferToZeroAddress(); 148 | 149 | Accruer storage fromAccruer = _accruers[from]; 150 | Accruer storage toAccruer = _accruers[to]; 151 | 152 | fromAccruer.balance = balanceOf(from) - amount; 153 | 154 | unchecked { 155 | toAccruer.balance += amount; 156 | } 157 | 158 | if (fromAccruer.accrualStartBlock != 0) { 159 | fromAccruer.accrualStartBlock = uint128(block.number); 160 | } 161 | 162 | emit Transfer(from, to, amount); 163 | } 164 | 165 | /*============================================================== 166 | == DRIP LOGIC == 167 | ==============================================================*/ 168 | 169 | /** 170 | * @dev Add an address to start dripping tokens to. 171 | * @dev We need to update _currAccrued whenever we add a new dripper or INCREASE a dripper multiplier to properly update totalSupply() 172 | * @dev IMPORTANT: Everytime you call this with an addr already getting dripped to, it will INCREASE the multiplier 173 | * @param addr address to drip to 174 | * @param multiplier used to increase token drip. ie if 1 NFT drips 10 tokens per block and this address has 3 NFTs, 175 | * the user would need to get dripped 30 tokens per block - multipler would multiply emissions by 3 176 | */ 177 | function _startDripping(address addr, uint128 multiplier) internal virtual { 178 | Accruer storage accruer = _accruers[addr]; 179 | 180 | // need to update the balance if wallet was already accruing 181 | if (accruer.accrualStartBlock != 0) { 182 | accruer.balance = balanceOf(addr); 183 | } else { 184 | // emit Transfer event when new address starts dripping 185 | emit Transfer(address(0), addr, 0); 186 | } 187 | 188 | _currAccrued = totalSupply(); 189 | _currEmissionBlockNum = uint128(block.number); 190 | accruer.accrualStartBlock = uint128(block.number); 191 | 192 | // should not overflow unless you have >2**256-1 items... 193 | unchecked { 194 | _currEmissionMultiple += multiplier; 195 | accruer.multiplier += multiplier; 196 | } 197 | } 198 | 199 | /** 200 | * @dev Add an address to stop dripping tokens to. 201 | * @dev We need to update _currAccrued whenever we remove a dripper or DECREASE a dripper multiplier to properly update totalSupply() 202 | * @dev IMPORTANT: Everytime you call this with an addr already getting dripped to, it will DECREASE the multiplier 203 | * @dev IMPORTANT: Decrease the multiplier to 0 to completely stop the address from getting dripped to 204 | * @param addr address to stop dripping to 205 | * @param multiplier used to decrease token drip. ie if addr has a multiplier of 3 already, passing in a value of 1 would decrease 206 | * the multiplier to 2 207 | */ 208 | function _stopDripping(address addr, uint128 multiplier) internal virtual { 209 | Accruer storage accruer = _accruers[addr]; 210 | 211 | // should I check for 0 multiplier too 212 | if (accruer.accrualStartBlock == 0) revert UserNotAccruing(); 213 | 214 | accruer.balance = balanceOf(addr); 215 | _currAccrued = totalSupply(); 216 | _currEmissionBlockNum = uint128(block.number); 217 | 218 | // will revert if underflow occurs 219 | _currEmissionMultiple -= multiplier; 220 | accruer.multiplier -= multiplier; 221 | 222 | if (accruer.multiplier == 0) { 223 | accruer.accrualStartBlock = 0; 224 | } else { 225 | accruer.accrualStartBlock = uint128(block.number); 226 | } 227 | } 228 | 229 | /*============================================================== 230 | == MINT/BURN == 231 | ==============================================================*/ 232 | 233 | function _mint(address to, uint256 amount) internal virtual { 234 | Accruer storage accruer = _accruers[to]; 235 | 236 | unchecked { 237 | _currAccrued += amount; 238 | accruer.balance += amount; 239 | } 240 | 241 | emit Transfer(address(0), to, amount); 242 | } 243 | 244 | function _burn(address from, uint256 amount) internal virtual { 245 | Accruer storage accruer = _accruers[from]; 246 | 247 | // have to update supply before burning 248 | _currAccrued = totalSupply(); 249 | _currEmissionBlockNum = uint128(block.number); 250 | 251 | accruer.balance = balanceOf(from) - amount; 252 | 253 | // Cannot underflow because amount can 254 | // never be greater than the totalSupply() 255 | unchecked { 256 | _currAccrued -= amount; 257 | } 258 | 259 | // update accruers block number if user was accruing 260 | if (accruer.accrualStartBlock != 0) { 261 | accruer.accrualStartBlock = uint128(block.number); 262 | } 263 | 264 | emit Transfer(from, address(0), amount); 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /test/DRIP20.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "ds-test/test.sol"; 5 | import {console} from "forge-std/console.sol"; 6 | import {stdError} from "forge-std/Test.sol"; 7 | import {MockDRIP20} from "./mocks/MockDRIP20.sol"; 8 | import {DRIP20} from "../src/DRIP20.sol"; 9 | 10 | interface Vm { 11 | function prank(address) external; 12 | 13 | function startPrank(address) external; 14 | 15 | function startPrank(address, address) external; 16 | 17 | function stopPrank() external; 18 | 19 | function roll(uint256) external; 20 | 21 | function expectRevert(bytes calldata) external; 22 | function expectRevert(bytes4) external; 23 | 24 | function expectEmit( 25 | bool, 26 | bool, 27 | bool, 28 | bool 29 | ) external; 30 | } 31 | 32 | contract DRIP20Test is DSTest { 33 | MockDRIP20 token; 34 | 35 | address user1; 36 | address user2; 37 | address user3; 38 | 39 | Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 40 | 41 | event Transfer(address indexed from, address indexed to, uint256 amount); 42 | 43 | function setUp() public { 44 | token = new MockDRIP20("MOCK", "MOCK", 18, 10); // token emission is 10 per block 45 | user1 = address(0xaa); 46 | user2 = address(0xbb); 47 | user3 = address(0xcc); 48 | } 49 | 50 | function testMint() public { 51 | vm.startPrank(user1); 52 | token.mint(1e18); 53 | assertEq(token.totalSupply(), 1e18); 54 | assertEq(token.balanceOf(user1), 1e18); 55 | } 56 | 57 | function testSimpleBurn() public { 58 | vm.startPrank(user1); 59 | token.mint(1e18); 60 | assertEq(token.totalSupply(), 1e18); 61 | assertEq(token.balanceOf(user1), 1e18); 62 | 63 | token.burn(user1, 0.5e18); 64 | assertEq(token.totalSupply(), 1e18 - 0.5e18); 65 | assertEq(token.balanceOf(user1), 1e18 - 0.5e18); 66 | } 67 | 68 | function testSimpleTransfer() public { 69 | vm.startPrank(user1); 70 | token.mint(1e18); 71 | token.transfer(user2, 0.5e18); 72 | 73 | assertEq(token.totalSupply(), 1e18); 74 | assertEq(token.balanceOf(user2), 0.5e18); 75 | assertEq(token.balanceOf(user1), 1e18 - 0.5e18); 76 | } 77 | 78 | function testSimpleTransferFrom() public { 79 | vm.startPrank(user1); 80 | token.mint(1e18); 81 | token.approve(address(this), type(uint256).max); 82 | vm.stopPrank(); 83 | 84 | token.transferFrom(user1, user2, 0.5e18); 85 | 86 | assertEq(token.totalSupply(), 1e18); 87 | assertEq(token.balanceOf(user2), 0.5e18); 88 | assertEq(token.balanceOf(user1), 1e18 - 0.5e18); 89 | } 90 | 91 | function testDripSingleUser() public { 92 | // need to start on a non zero block number since we use block 0 as 'not dripping' 93 | vm.roll(1); 94 | 95 | // check event is emitted 96 | vm.expectEmit(true, true, true, true); 97 | emit Transfer(address(0), user1, 0); 98 | 99 | token.startDripping(user1); 100 | 101 | assertEq(token.totalSupply(), 0); 102 | assertEq(token.balanceOf(user1), 0); 103 | 104 | // should accumulate 4 * 10 tokens 105 | vm.roll(5); 106 | 107 | assertEq(token.totalSupply(), 40); 108 | assertEq(token.balanceOf(user1), 40); 109 | } 110 | 111 | function testDripMultiUser() public { 112 | // need to start on a non zero block number since we use block 0 as 'not dripping' 113 | vm.roll(1); 114 | token.startDripping(user1); 115 | vm.roll(5); 116 | 117 | assertEq(token.totalSupply(), 40); 118 | assertEq(token.balanceOf(user1), 40); 119 | 120 | token.startDripping(user2); 121 | token.startDripping(user3); 122 | 123 | // nothing should change since we didnt roll blocks 124 | assertEq(token.totalSupply(), 40); 125 | assertEq(token.balanceOf(user1), 40); 126 | assertEq(token.balanceOf(user2), 0); 127 | assertEq(token.balanceOf(user3), 0); 128 | 129 | // roll 5 blocks, each user should've accrued 50 tokens 130 | vm.roll(10); 131 | 132 | assertEq(token.totalSupply(), 40 + 50 * 3); 133 | assertEq(token.balanceOf(user1), 40 + 50); 134 | assertEq(token.balanceOf(user2), 50); 135 | assertEq(token.balanceOf(user3), 50); 136 | } 137 | 138 | function testStopDripSingleUser() public { 139 | // need to start on a non zero block number since we use block 0 as 'not dripping' 140 | vm.roll(1); 141 | token.startDripping(user1); 142 | vm.roll(5); 143 | 144 | assertEq(token.totalSupply(), 40); 145 | assertEq(token.balanceOf(user1), 40); 146 | 147 | token.stopDripping(user1); 148 | 149 | // nothing should change since we didnt roll blocks 150 | assertEq(token.totalSupply(), 40); 151 | assertEq(token.balanceOf(user1), 40); 152 | 153 | // roll 5 blocks, no changes should occur 154 | vm.roll(10); 155 | 156 | assertEq(token.totalSupply(), 40); 157 | assertEq(token.balanceOf(user1), 40); 158 | } 159 | 160 | function testStopDripMultiUser() public { 161 | // need to start on a non zero block number since we use block 0 as 'not dripping' 162 | vm.roll(1); 163 | token.startDripping(user1); 164 | token.startDripping(user2); 165 | token.startDripping(user3); 166 | vm.roll(5); 167 | 168 | assertEq(token.totalSupply(), 40 * 3); 169 | assertEq(token.balanceOf(user1), 40); 170 | assertEq(token.balanceOf(user2), 40); 171 | assertEq(token.balanceOf(user3), 40); 172 | 173 | token.stopDripping(user1); 174 | token.stopDripping(user2); 175 | 176 | // nothing should change since we didnt roll blocks 177 | assertEq(token.totalSupply(), 40 * 3); 178 | assertEq(token.balanceOf(user1), 40); 179 | assertEq(token.balanceOf(user2), 40); 180 | assertEq(token.balanceOf(user3), 40); 181 | 182 | // roll 5 blocks, only user 3 should accrue 50 tokens 183 | vm.roll(10); 184 | 185 | assertEq(token.totalSupply(), 120 + 50); 186 | assertEq(token.balanceOf(user1), 40); 187 | assertEq(token.balanceOf(user2), 40); 188 | assertEq(token.balanceOf(user3), 40 + 50); 189 | } 190 | 191 | function testTransferFromDrippingUserToNonDrippingUser() public { 192 | // need to start on a non zero block number since we use block 0 as 'not dripping' 193 | vm.roll(1); 194 | token.startDripping(user1); 195 | vm.roll(5); 196 | 197 | assertEq(token.totalSupply(), 40); 198 | assertEq(token.balanceOf(user1), 40); 199 | 200 | vm.prank(user1); 201 | token.transfer(user2, 20); 202 | 203 | assertEq(token.totalSupply(), 40); 204 | assertEq(token.balanceOf(user1), 20); 205 | assertEq(token.balanceOf(user2), 20); 206 | 207 | // roll 5 blocks, user1 should've accrued 50 tokens 208 | vm.roll(10); 209 | 210 | assertEq(token.totalSupply(), 40 + 50); 211 | assertEq(token.balanceOf(user1), 20 + 50); 212 | assertEq(token.balanceOf(user2), 20); 213 | } 214 | 215 | function testTransferFromDrippingUserToDrippingUser() public { 216 | // need to start on a non zero block number since we use block 0 as 'not dripping' 217 | vm.roll(1); 218 | token.startDripping(user1); 219 | token.startDripping(user2); 220 | vm.roll(5); 221 | 222 | assertEq(token.totalSupply(), 80); 223 | assertEq(token.balanceOf(user1), 40); 224 | assertEq(token.balanceOf(user2), 40); 225 | 226 | vm.prank(user1); 227 | token.transfer(user2, 20); 228 | 229 | assertEq(token.totalSupply(), 80); 230 | assertEq(token.balanceOf(user1), 40 - 20); 231 | assertEq(token.balanceOf(user2), 40 + 20); 232 | 233 | // roll 5 blocks, user1 and 2 should've accrued 50 tokens 234 | vm.roll(10); 235 | 236 | assertEq(token.totalSupply(), 80 + 50 * 2); 237 | assertEq(token.balanceOf(user1), 20 + 50); 238 | assertEq(token.balanceOf(user2), 60 + 50); 239 | } 240 | 241 | function testTransferFromNonDrippingUserToDrippingUser() public { 242 | // need to start on a non zero block number since we use block 0 as 'not dripping' 243 | vm.roll(1); 244 | token.startDripping(user1); 245 | vm.roll(5); 246 | 247 | vm.prank(user2); 248 | token.mint(100); 249 | 250 | assertEq(token.totalSupply(), 140); 251 | assertEq(token.balanceOf(user1), 40); 252 | assertEq(token.balanceOf(user2), 100); 253 | 254 | vm.prank(user2); 255 | token.transfer(user1, 50); 256 | 257 | assertEq(token.totalSupply(), 140); 258 | assertEq(token.balanceOf(user1), 40 + 50); 259 | assertEq(token.balanceOf(user2), 100 - 50); 260 | 261 | // roll 5 blocks, user1 should've accrued 50 tokens 262 | vm.roll(10); 263 | 264 | assertEq(token.totalSupply(), 140 + 50); 265 | assertEq(token.balanceOf(user1), 90 + 50); 266 | assertEq(token.balanceOf(user2), 50); 267 | } 268 | 269 | function testBurnFromDrippingUser() public { 270 | // need to start on a non zero block number since we use block 0 as 'not dripping' 271 | vm.roll(1); 272 | token.startDripping(user1); 273 | vm.roll(5); 274 | 275 | assertEq(token.totalSupply(), 40); 276 | assertEq(token.balanceOf(user1), 40); 277 | 278 | token.burn(user1, 40); 279 | 280 | // nothing should change since we didnt roll blocks 281 | assertEq(token.totalSupply(), 0); 282 | assertEq(token.balanceOf(user1), 0); 283 | 284 | // roll 5 blocks, no changes should occur 285 | vm.roll(10); 286 | 287 | assertEq(token.totalSupply(), 50); 288 | assertEq(token.balanceOf(user1), 50); 289 | } 290 | 291 | function testRevertTransferMoreThanBalance() public { 292 | vm.startPrank(user1); 293 | token.mint(40); 294 | 295 | vm.expectRevert(stdError.arithmeticError); 296 | token.transfer(user2, 50); 297 | } 298 | 299 | function testRevertBurnMoreThanBalance() public { 300 | vm.startPrank(user1); 301 | token.mint(40); 302 | 303 | vm.expectRevert(stdError.arithmeticError); 304 | token.burn(user1, 50); 305 | } 306 | 307 | function testRevertStartDrip() public { 308 | // need to start on a non zero block number since we use block 0 as 'not dripping' 309 | vm.roll(1); 310 | token.startDripping(user1); 311 | vm.expectRevert(DRIP20.UserAlreadyAccruing.selector); 312 | token.startDripping(user1); 313 | } 314 | 315 | function testRevertStopDrip() public { 316 | // need to start on a non zero block number since we use block 0 as 'not dripping' 317 | vm.roll(1); 318 | vm.expectRevert(DRIP20.UserNotAccruing.selector); 319 | token.stopDripping(user1); 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /test/GIGADRIP20.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "ds-test/test.sol"; 5 | import {console} from "forge-std/console.sol"; 6 | import {stdError} from "forge-std/Test.sol"; 7 | import {MockGIGADRIP20} from "./mocks/MockGIGADRIP20.sol"; 8 | 9 | interface Vm { 10 | function prank(address) external; 11 | 12 | function startPrank(address) external; 13 | 14 | function startPrank(address, address) external; 15 | 16 | function stopPrank() external; 17 | 18 | function roll(uint256) external; 19 | 20 | function expectRevert(bytes calldata) external; 21 | 22 | function expectEmit( 23 | bool, 24 | bool, 25 | bool, 26 | bool 27 | ) external; 28 | } 29 | 30 | contract GIGADRIP20Test is DSTest { 31 | MockGIGADRIP20 token; 32 | 33 | address user1; 34 | address user2; 35 | address user3; 36 | 37 | Vm vm = Vm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); 38 | 39 | event Transfer(address indexed from, address indexed to, uint256 amount); 40 | 41 | function setUp() public { 42 | token = new MockGIGADRIP20("MOCK", "MOCK", 18, 10); // token emission is 10 per block 43 | user1 = address(0xaa); 44 | user2 = address(0xbb); 45 | user3 = address(0xcc); 46 | } 47 | 48 | function testStreamSingleUserMultiplier() public { 49 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 50 | vm.roll(1); 51 | token.startDripping(user1, 3); 52 | 53 | assertEq(token.totalSupply(), 0); 54 | assertEq(token.balanceOf(user1), 0); 55 | 56 | // should accumulate 4 * 10 * 3 tokens 57 | vm.roll(5); 58 | 59 | assertEq(token.totalSupply(), 120); 60 | assertEq(token.balanceOf(user1), 120); 61 | } 62 | 63 | // Checks to see if adding a user multiple times to a drip increases multiplier correctly 64 | // Also check events are emitted correctly 65 | function testStreamSingUserMultiDrip() public { 66 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 67 | vm.roll(1); 68 | // check event is emitted 69 | vm.expectEmit(true, true, true, true); 70 | emit Transfer(address(0), user1, 0); 71 | // multiplier should add to 3 72 | token.startDripping(user1, 1); 73 | token.startDripping(user1, 1); 74 | token.startDripping(user1, 1); 75 | vm.roll(5); 76 | 77 | assertEq(token.totalSupply(), 120); 78 | assertEq(token.balanceOf(user1), 120); 79 | 80 | vm.expectEmit(true, true, true, true); 81 | emit Transfer(address(0), user2, 0); 82 | token.startDripping(user2, 1); 83 | 84 | vm.expectEmit(true, true, true, true); 85 | emit Transfer(address(0), user3, 0); 86 | token.startDripping(user3, 2); 87 | 88 | // nothing should change since we didnt roll blocks 89 | assertEq(token.totalSupply(), 120); 90 | assertEq(token.balanceOf(user1), 120); 91 | assertEq(token.balanceOf(user2), 0); 92 | assertEq(token.balanceOf(user3), 0); 93 | 94 | // roll 5 blocks, each user should've accrued 50 * multiplier tokens 95 | vm.roll(10); 96 | 97 | assertEq(token.totalSupply(), 120 + 50 * 6); 98 | assertEq(token.balanceOf(user1), 120 + 50 * 3); 99 | assertEq(token.balanceOf(user2), 50); 100 | assertEq(token.balanceOf(user3), 50 * 2); 101 | } 102 | 103 | function testStreamMultiUserMultiplier() public { 104 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 105 | vm.roll(1); 106 | // multiplier == 3, ie if someone minted 3 items each accumulating tokens 107 | token.startDripping(user1, 3); 108 | vm.roll(5); 109 | 110 | assertEq(token.totalSupply(), 120); 111 | assertEq(token.balanceOf(user1), 120); 112 | 113 | token.startDripping(user2, 1); 114 | token.startDripping(user3, 2); 115 | 116 | // nothing should change since we didnt roll blocks 117 | assertEq(token.totalSupply(), 120); 118 | assertEq(token.balanceOf(user1), 120); 119 | assertEq(token.balanceOf(user2), 0); 120 | assertEq(token.balanceOf(user3), 0); 121 | 122 | // roll 5 blocks, each user should've accrued 50 * multiplier tokens 123 | vm.roll(10); 124 | 125 | assertEq(token.totalSupply(), 120 + 50 * 6); 126 | assertEq(token.balanceOf(user1), 120 + 50 * 3); 127 | assertEq(token.balanceOf(user2), 50); 128 | assertEq(token.balanceOf(user3), 50 * 2); 129 | } 130 | 131 | function testStopStreamMultiUserMultiplier() public { 132 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 133 | vm.roll(1); 134 | token.startDripping(user1, 3); 135 | token.startDripping(user2, 3); 136 | token.startDripping(user3, 1); 137 | vm.roll(5); 138 | 139 | assertEq(token.totalSupply(), 40 * 7); 140 | assertEq(token.balanceOf(user1), 40 * 3); 141 | assertEq(token.balanceOf(user2), 40 * 3); 142 | assertEq(token.balanceOf(user3), 40); 143 | 144 | // user1 multiplier should be 2 now 145 | token.stopDripping(user1, 1); 146 | // user2 should stop accruing 147 | token.stopDripping(user2, 3); 148 | 149 | // nothing should change since we didnt roll blocks 150 | assertEq(token.totalSupply(), 280); 151 | assertEq(token.balanceOf(user1), 120); 152 | assertEq(token.balanceOf(user2), 120); 153 | assertEq(token.balanceOf(user3), 40); 154 | 155 | // roll 5 blocks, each user should've accrued 50 * multiplier tokens 156 | vm.roll(10); 157 | 158 | assertEq(token.totalSupply(), 280 + 50 * 3); // user1 + user3 multiplier is 3 159 | assertEq(token.balanceOf(user1), 120 + 50 * 2); 160 | assertEq(token.balanceOf(user2), 120); 161 | assertEq(token.balanceOf(user3), 40 + 50); 162 | } 163 | 164 | function testStopStreamRevert() public { 165 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 166 | vm.roll(1); 167 | token.startDripping(user1, 3); 168 | vm.expectRevert(stdError.arithmeticError); 169 | token.stopDripping(user1, 4); 170 | } 171 | 172 | function testTransferFromDrippingUserToNonDrippingUserMultiplier() public { 173 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 174 | vm.roll(1); 175 | token.startDripping(user1, 3); 176 | vm.roll(5); 177 | 178 | assertEq(token.totalSupply(), 120); 179 | assertEq(token.balanceOf(user1), 120); 180 | 181 | vm.prank(user1); 182 | token.transfer(user2, 20); 183 | 184 | assertEq(token.totalSupply(), 120); 185 | assertEq(token.balanceOf(user1), 100); 186 | assertEq(token.balanceOf(user2), 20); 187 | 188 | // roll 5 blocks, each user should've accrued 50 * multiplier tokens 189 | vm.roll(10); 190 | 191 | assertEq(token.totalSupply(), 120 + 50 * 3); 192 | assertEq(token.balanceOf(user1), 100 + 50 * 3); 193 | assertEq(token.balanceOf(user2), 20); 194 | } 195 | 196 | function testTransferFromDrippingUserToDrippingUserMultiplier() public { 197 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 198 | vm.roll(1); 199 | token.startDripping(user1, 3); 200 | token.startDripping(user2, 1); 201 | vm.roll(5); 202 | 203 | assertEq(token.totalSupply(), 160); 204 | assertEq(token.balanceOf(user1), 120); 205 | assertEq(token.balanceOf(user2), 40); 206 | 207 | vm.prank(user1); 208 | token.transfer(user2, 20); 209 | 210 | assertEq(token.totalSupply(), 160); 211 | assertEq(token.balanceOf(user1), 120 - 20); 212 | assertEq(token.balanceOf(user2), 40 + 20); 213 | 214 | // roll 5 blocks, each user should've accrued 50 * multiplier tokens 215 | vm.roll(10); 216 | 217 | assertEq(token.totalSupply(), 160 + 50 * 4); 218 | assertEq(token.balanceOf(user1), 100 + 50 * 3); 219 | assertEq(token.balanceOf(user2), 60 + 50); 220 | } 221 | 222 | function testTransferFromNonDrippingUserToDrippingUserMultiplier() public { 223 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 224 | vm.roll(1); 225 | token.startDripping(user1, 3); 226 | vm.roll(5); 227 | 228 | vm.prank(user2); 229 | token.mint(100); 230 | 231 | assertEq(token.totalSupply(), 120 + 100); 232 | assertEq(token.balanceOf(user1), 120); 233 | assertEq(token.balanceOf(user2), 100); 234 | 235 | vm.prank(user2); 236 | token.transfer(user1, 50); 237 | 238 | assertEq(token.totalSupply(), 220); 239 | assertEq(token.balanceOf(user1), 120 + 50); 240 | assertEq(token.balanceOf(user2), 100 - 50); 241 | 242 | // roll 5 blocks, each user should've accrued 50 * multiplier tokens 243 | vm.roll(10); 244 | 245 | assertEq(token.totalSupply(), 220 + 50 * 3); 246 | assertEq(token.balanceOf(user1), 170 + 50 * 3); 247 | assertEq(token.balanceOf(user2), 50); 248 | } 249 | 250 | function testBurnFromDrippingUserMultiplier() public { 251 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 252 | vm.roll(1); 253 | token.startDripping(user1, 3); 254 | vm.roll(5); 255 | 256 | assertEq(token.totalSupply(), 120); 257 | assertEq(token.balanceOf(user1), 120); 258 | 259 | token.burn(user1, 40); 260 | 261 | // nothing should change since we didnt roll blocks 262 | assertEq(token.totalSupply(), 80); 263 | assertEq(token.balanceOf(user1), 80); 264 | 265 | // roll 5 blocks, each user should've accrued 50 * multiplier tokens 266 | vm.roll(10); 267 | 268 | assertEq(token.totalSupply(), 80 + 50 * 3); 269 | assertEq(token.balanceOf(user1), 80 + 50 * 3); 270 | } 271 | 272 | // Below tests are the same tests for DRIP20 - to make sure nothing broke 273 | 274 | function testMint() public { 275 | vm.startPrank(user1); 276 | token.mint(1e18); 277 | assertEq(token.totalSupply(), 1e18); 278 | assertEq(token.balanceOf(user1), 1e18); 279 | } 280 | 281 | function testSimpleBurn() public { 282 | vm.startPrank(user1); 283 | token.mint(1e18); 284 | assertEq(token.totalSupply(), 1e18); 285 | assertEq(token.balanceOf(user1), 1e18); 286 | 287 | token.burn(user1, 0.5e18); 288 | assertEq(token.totalSupply(), 1e18 - 0.5e18); 289 | assertEq(token.balanceOf(user1), 1e18 - 0.5e18); 290 | } 291 | 292 | function testSimpleTransfer() public { 293 | vm.startPrank(user1); 294 | token.mint(1e18); 295 | token.transfer(user2, 0.5e18); 296 | 297 | assertEq(token.totalSupply(), 1e18); 298 | assertEq(token.balanceOf(user2), 0.5e18); 299 | assertEq(token.balanceOf(user1), 1e18 - 0.5e18); 300 | } 301 | 302 | function testSimpleTransferFrom() public { 303 | vm.startPrank(user1); 304 | token.mint(1e18); 305 | token.approve(address(this), type(uint256).max); 306 | vm.stopPrank(); 307 | 308 | token.transferFrom(user1, user2, 0.5e18); 309 | 310 | assertEq(token.totalSupply(), 1e18); 311 | assertEq(token.balanceOf(user2), 0.5e18); 312 | assertEq(token.balanceOf(user1), 1e18 - 0.5e18); 313 | } 314 | 315 | function testStreamSingleUser() public { 316 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 317 | vm.roll(1); 318 | token.startDripping(user1, 1); 319 | 320 | assertEq(token.totalSupply(), 0); 321 | assertEq(token.balanceOf(user1), 0); 322 | 323 | // should accumulat 4 * 10 tokens 324 | vm.roll(5); 325 | 326 | assertEq(token.totalSupply(), 40); 327 | assertEq(token.balanceOf(user1), 40); 328 | } 329 | 330 | function testStreamMultiUser() public { 331 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 332 | vm.roll(1); 333 | token.startDripping(user1, 1); 334 | vm.roll(5); 335 | 336 | assertEq(token.totalSupply(), 40); 337 | assertEq(token.balanceOf(user1), 40); 338 | 339 | token.startDripping(user2, 1); 340 | token.startDripping(user3, 1); 341 | 342 | // nothing should change since we didnt roll blocks 343 | assertEq(token.totalSupply(), 40); 344 | assertEq(token.balanceOf(user1), 40); 345 | assertEq(token.balanceOf(user2), 0); 346 | assertEq(token.balanceOf(user3), 0); 347 | 348 | // roll 5 blocks, each user should've accrued 50 tokens 349 | vm.roll(10); 350 | 351 | assertEq(token.totalSupply(), 40 + 50 * 3); 352 | assertEq(token.balanceOf(user1), 40 + 50); 353 | assertEq(token.balanceOf(user2), 50); 354 | assertEq(token.balanceOf(user3), 50); 355 | } 356 | 357 | function testStopStreamSingleUser() public { 358 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 359 | vm.roll(1); 360 | token.startDripping(user1, 1); 361 | vm.roll(5); 362 | 363 | assertEq(token.totalSupply(), 40); 364 | assertEq(token.balanceOf(user1), 40); 365 | 366 | token.stopDripping(user1, 1); 367 | 368 | // nothing should change since we didnt roll blocks 369 | assertEq(token.totalSupply(), 40); 370 | assertEq(token.balanceOf(user1), 40); 371 | 372 | // roll 5 blocks, no changes should occur 373 | vm.roll(10); 374 | 375 | assertEq(token.totalSupply(), 40); 376 | assertEq(token.balanceOf(user1), 40); 377 | } 378 | 379 | function testStopStreamMultiUser() public { 380 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 381 | vm.roll(1); 382 | token.startDripping(user1, 1); 383 | token.startDripping(user2, 1); 384 | token.startDripping(user3, 1); 385 | vm.roll(5); 386 | 387 | assertEq(token.totalSupply(), 40 * 3); 388 | assertEq(token.balanceOf(user1), 40); 389 | assertEq(token.balanceOf(user2), 40); 390 | assertEq(token.balanceOf(user3), 40); 391 | 392 | token.stopDripping(user1, 1); 393 | token.stopDripping(user2, 1); 394 | 395 | // nothing should change since we didnt roll blocks 396 | assertEq(token.totalSupply(), 40 * 3); 397 | assertEq(token.balanceOf(user1), 40); 398 | assertEq(token.balanceOf(user2), 40); 399 | assertEq(token.balanceOf(user3), 40); 400 | 401 | // roll 5 blocks, only user 3 should accrue 50 tokens 402 | vm.roll(10); 403 | 404 | assertEq(token.totalSupply(), 120 + 50); 405 | assertEq(token.balanceOf(user1), 40); 406 | assertEq(token.balanceOf(user2), 40); 407 | assertEq(token.balanceOf(user3), 40 + 50); 408 | } 409 | 410 | function testTransferFromDrippingUserToNonDrippingUser() public { 411 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 412 | vm.roll(1); 413 | token.startDripping(user1, 1); 414 | vm.roll(5); 415 | 416 | assertEq(token.totalSupply(), 40); 417 | assertEq(token.balanceOf(user1), 40); 418 | 419 | vm.prank(user1); 420 | token.transfer(user2, 20); 421 | 422 | assertEq(token.totalSupply(), 40); 423 | assertEq(token.balanceOf(user1), 20); 424 | assertEq(token.balanceOf(user2), 20); 425 | 426 | // roll 5 blocks, user1 should've accrued 50 tokens 427 | vm.roll(10); 428 | 429 | assertEq(token.totalSupply(), 40 + 50); 430 | assertEq(token.balanceOf(user1), 20 + 50); 431 | assertEq(token.balanceOf(user2), 20); 432 | } 433 | 434 | function testTransferFromDrippingUserToDrippingUser() public { 435 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 436 | vm.roll(1); 437 | token.startDripping(user1, 1); 438 | token.startDripping(user2, 1); 439 | vm.roll(5); 440 | 441 | assertEq(token.totalSupply(), 80); 442 | assertEq(token.balanceOf(user1), 40); 443 | assertEq(token.balanceOf(user2), 40); 444 | 445 | vm.prank(user1); 446 | token.transfer(user2, 20); 447 | 448 | assertEq(token.totalSupply(), 80); 449 | assertEq(token.balanceOf(user1), 40 - 20); 450 | assertEq(token.balanceOf(user2), 40 + 20); 451 | 452 | // roll 5 blocks, user1 and 2 should've accrued 50 tokens 453 | vm.roll(10); 454 | 455 | assertEq(token.totalSupply(), 80 + 50 * 2); 456 | assertEq(token.balanceOf(user1), 20 + 50); 457 | assertEq(token.balanceOf(user2), 60 + 50); 458 | } 459 | 460 | function testTransferFromNonDrippingUserToDrippingUser() public { 461 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 462 | vm.roll(1); 463 | token.startDripping(user1, 1); 464 | vm.roll(5); 465 | 466 | vm.prank(user2); 467 | token.mint(100); 468 | 469 | assertEq(token.totalSupply(), 140); 470 | assertEq(token.balanceOf(user1), 40); 471 | assertEq(token.balanceOf(user2), 100); 472 | 473 | vm.prank(user2); 474 | token.transfer(user1, 50); 475 | 476 | assertEq(token.totalSupply(), 140); 477 | assertEq(token.balanceOf(user1), 40 + 50); 478 | assertEq(token.balanceOf(user2), 100 - 50); 479 | 480 | // roll 5 blocks, user1 should've accrued 50 tokens 481 | vm.roll(10); 482 | 483 | assertEq(token.totalSupply(), 140 + 50); 484 | assertEq(token.balanceOf(user1), 90 + 50); 485 | assertEq(token.balanceOf(user2), 50); 486 | } 487 | 488 | function testBurnFromDrippingUser() public { 489 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 490 | vm.roll(1); 491 | token.startDripping(user1, 1); 492 | vm.roll(5); 493 | 494 | assertEq(token.totalSupply(), 40); 495 | assertEq(token.balanceOf(user1), 40); 496 | 497 | token.burn(user1, 40); 498 | 499 | // nothing should change since we didnt roll blocks 500 | assertEq(token.totalSupply(), 0); 501 | assertEq(token.balanceOf(user1), 0); 502 | 503 | // roll 5 blocks, no changes should occur 504 | vm.roll(10); 505 | 506 | assertEq(token.totalSupply(), 50); 507 | assertEq(token.balanceOf(user1), 50); 508 | } 509 | 510 | function testBurnFromNonDrippingUser() public { 511 | // need to start on a non zero block number since we use block 0 as 'not Dripping' 512 | vm.roll(1); 513 | token.startDripping(user1, 1); 514 | vm.roll(5); 515 | 516 | vm.prank(user1); 517 | token.transfer(user2, 40); 518 | 519 | assertEq(token.totalSupply(), 40); 520 | assertEq(token.balanceOf(user1), 0); 521 | assertEq(token.balanceOf(user2), 40); 522 | 523 | token.burn(user2, 40); 524 | 525 | // nothing should change since we didnt roll blocks 526 | assertEq(token.totalSupply(), 0); 527 | assertEq(token.balanceOf(user1), 0); 528 | assertEq(token.balanceOf(user2), 0); 529 | 530 | // roll 5 blocks, no changes should occur 531 | vm.roll(10); 532 | 533 | assertEq(token.totalSupply(), 50); 534 | assertEq(token.balanceOf(user1), 50); 535 | assertEq(token.balanceOf(user2), 0); 536 | } 537 | } 538 | -------------------------------------------------------------------------------- /test/mocks/MockDRIP20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | 3 | import "../../src/DRIP20.sol"; 4 | 5 | contract MockDRIP20 is DRIP20 { 6 | constructor( 7 | string memory _name, 8 | string memory _symbol, 9 | uint8 _decimals, 10 | uint256 _emissionRatePerBlock 11 | ) DRIP20(_name, _symbol, _decimals, _emissionRatePerBlock) {} 12 | 13 | function mint(uint256 amount) external { 14 | _mint(msg.sender, amount); 15 | } 16 | 17 | function startDripping(address addr) external virtual { 18 | _startDripping(addr); 19 | } 20 | 21 | function stopDripping(address addr) external virtual { 22 | _stopDripping(addr); 23 | } 24 | 25 | function burn(address from, uint256 value) external virtual { 26 | _burn(from, value); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/mocks/MockGIGADRIP20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.0; 2 | import {console} from "forge-std/console.sol"; 3 | import "../../src/GIGADRIP20.sol"; 4 | 5 | contract MockGIGADRIP20 is GIGADRIP20 { 6 | constructor( 7 | string memory _name, 8 | string memory _symbol, 9 | uint8 _decimals, 10 | uint128 _emissionRatePerBlock 11 | ) GIGADRIP20(_name, _symbol, _decimals, _emissionRatePerBlock) {} 12 | 13 | function mint(uint256 amount) external { 14 | _mint(msg.sender, amount); 15 | } 16 | 17 | function startDripping(address addr, uint128 multiplier) external virtual { 18 | _startDripping(addr, multiplier); 19 | } 20 | 21 | function stopDripping(address addr, uint128 multiplier) external virtual { 22 | _stopDripping(addr, multiplier); 23 | } 24 | 25 | function burn(address from, uint256 value) external virtual { 26 | _burn(from, value); 27 | } 28 | } 29 | --------------------------------------------------------------------------------