{"id":13579521,"url":"https://github.com/d-xo/weird-erc20","last_synced_at":"2025-05-15T05:06:13.441Z","repository":{"id":38019365,"uuid":"289896413","full_name":"d-xo/weird-erc20","owner":"d-xo","description":"weird erc20 tokens","archived":false,"fork":false,"pushed_at":"2024-08-06T09:51:17.000Z","size":131,"stargazers_count":1548,"open_issues_count":13,"forks_count":196,"subscribers_count":23,"default_branch":"main","last_synced_at":"2025-05-14T03:24:08.432Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Solidity","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/d-xo.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-08-24T10:22:53.000Z","updated_at":"2025-05-09T16:09:44.000Z","dependencies_parsed_at":"2024-08-01T15:30:52.290Z","dependency_job_id":"4bcf0886-5e8e-4db7-ba95-f030ce91eb07","html_url":"https://github.com/d-xo/weird-erc20","commit_stats":{"total_commits":50,"total_committers":13,"mean_commits":"3.8461538461538463","dds":"0.33999999999999997","last_synced_commit":"266025c555b42b2dd2517fd99f7d47032ec99abe"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-xo%2Fweird-erc20","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-xo%2Fweird-erc20/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-xo%2Fweird-erc20/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/d-xo%2Fweird-erc20/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/d-xo","download_url":"https://codeload.github.com/d-xo/weird-erc20/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254276447,"owners_count":22043867,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T15:01:40.163Z","updated_at":"2025-05-15T05:06:08.429Z","avatar_url":"https://github.com/d-xo.png","language":"Solidity","funding_links":[],"categories":["2. **ERC Standards \u0026 Edge Cases**","Resources","Solidity"],"sub_categories":["ERC20"],"readme":"# Weird ERC20 Tokens\n\nThis repository contains minimal example implementations in Solidity of ERC20 tokens with behaviour\nthat may be surprising or unexpected. All the tokens in this repo are based on real tokens, many of\nwhich have been used to exploit smart contract systems in the past. It is hoped that these example\nimplementations will be of use to developers and auditors.\n\nThe `ERC20` \"specification\" is so loosely defined that it amounts to little more than an interface\ndeclaration, and even the few semantic requirements that are imposed are routinely violated by token\ndevelopers in the wild.\n\nThis makes building smart contracts that interface directly with ERC20 tokens challenging to say the\nleast, and smart contract developers should in general default to the following patterns when\ninteraction with external code is required:\n\n1. A contract level allowlist of known good tokens.\n2. Direct interaction with tokens should be performed in dedicated wrapper contracts at the edge of\n   the system. This allows the core to assume a consistent and known good semantics for the\n   behaviour of external assets.\n\nIn some cases the above patterns are not practical (for example in the case of a permissionless AMM,\nkeeping an on chain allowlist would require the introduction of centralized control or a complex\ngovernance system), and in these cases developers must take great care to make these interactions in\na highly defensive manner. It should be noted that even if an onchain allowlist is not feasible, an\noffchain allowlist in the official UI can also protect unsophisticated users from tokens that\nviolate the contracts expectations, while still preserving contract level permissionlessness.\n\nFinally if you are building a token, you are strongly advised to treat the following as a list of\nbehaviours to avoid.\n\n*Additional Resources*\n\n- Trail of Bits [token integration checklist](https://github.com/crytic/building-secure-contracts/blob/master/development-guidelines/token_integration.md).\n- Consensys Diligence [token integration checklist](https://consensys.net/diligence/blog/2020/11/token-interaction-checklist/)\n\n# Tokens\n\n## Reentrant Calls\n\nSome tokens allow reentrant calls on transfer (e.g. `ERC777` tokens).\n\nThis has been exploited in the wild on multiple occasions (e.g. [imBTC Uniswap pool\ndrained](https://defirate.com/imbtc-uniswap-hack/), [lendf.me\ndrained](https://defirate.com/dforce-hack/))\n\n*example*: [Reentrant.sol](./src/Reentrant.sol)\n\n## Missing Return Values\n\nSome tokens do not return a bool (e.g. `USDT`, `BNB`, `OMG`) on ERC20 methods. see\n[here](https://gist.githubusercontent.com/lukas-berlin/f587086f139df93d22987049f3d8ebd2/raw/1f937dc8eb1d6018da59881cbc633e01c0286fb0/Tokens%20missing%20return%20values%20in%20transfer) for a comprehensive (if somewhat outdated) list.\n\nSome tokens (e.g. `BNB`) may return a `bool` for some methods, but fail to do so for others.  This\nresulted in stuck `BNB` tokens in Uniswap v1\n([details](https://mobile.twitter.com/UniswapProtocol/status/1072286773554876416)).\n\nSome particularly pathological tokens (e.g. Tether Gold) declare a bool return, but then return\n`false` even when the transfer was successful\n([code](https://etherscan.io/address/0x4922a015c4407f87432b179bb209e125432e4a2a#code)).\n\nA good safe transfer abstraction\n([example](https://github.com/Uniswap/uniswap-v2-core/blob/4dd59067c76dea4a0e8e4bfdda41877a6b16dedc/contracts/UniswapV2Pair.sol#L44))\ncan help somewhat, but note that the existence of Tether Gold makes it impossible to correctly handle\nreturn values for all tokens.\n\nTwo example tokens are provided:\n\n- `MissingReturns`: does not return a bool for any erc20 operation\n- `ReturnsFalse`: declares a bool return, but then returns false for every erc20 operation\n\n*example*: [MissingReturns.sol](./src/MissingReturns.sol)  \n*example*: [ReturnsFalse.sol](./src/ReturnsFalse.sol)\n\n## Fee on Transfer\n\nSome tokens take a transfer fee (e.g. `STA`, `PAXG`), some do not currently charge a fee but may do\nso in the future (e.g. `USDT`, `USDC`).\n\nThe `STA` transfer fee was used to drain $500k from several balancer pools ([more\ndetails](https://medium.com/@1inch.exchange/balancer-hack-2020-a8f7131c980e)).\n\n*example*: [TransferFee.sol](./src/TransferFee.sol)\n\n## Balance Modifications Outside of Transfers (rebasing/airdrops)\n\nSome tokens may make arbitrary balance modifications outside of transfers (e.g. Ampleforth style\nrebasing tokens, Compound style airdrops of governance tokens, mintable/burnable tokens).\n\nSome smart contract systems cache token balances (e.g. Balancer, Uniswap-V2), and arbitrary\nmodifications to underlying balances can mean that the contract is operating with outdated\ninformation.\n\nIn the case of Ampleforth, some Balancer and Uniswap pools are special cased to ensure that the\npool's cached balances are atomically updated as part of the rebase procedure\n([details](https://www.ampltalk.org/app/forum/technology-development-17/topic/supported-dex-pools-61/)).\n\n*example*: TODO: implement a rebasing token\n\n## Upgradable Tokens\n\nSome tokens (e.g. `USDC`, `USDT`) are upgradable, allowing the token owners to make arbitrary\nmodifications to the logic of the token at any point in time.\n\nA change to the token semantics can break any smart contract that depends on the past behaviour.\n\nDevelopers integrating with upgradable tokens should consider introducing logic that will freeze\ninteractions with the token in question if an upgrade is detected. (e.g. the [`TUSD`\nadapter](https://github.com/makerdao/dss-deploy/blob/7394f6555daf5747686a1b29b2f46c6b2c64b061/src/join.sol#L321)\nused by MakerDAO).\n\n*example*: [Upgradable.sol](./src/Upgradable.sol)\n\n## Flash Mintable Tokens\n\nSome tokens (e.g. `DAI`) allow for so called \"flash minting\", which allows tokens to be minted for the duration\nof one transaction only, provided they are returned to the token contract by the end of the\ntransaction.\n\nThis is similar to a flash loan, but does not require the tokens that are to be lent to exist before\nthe start of the transaction. A token that can be flash minted could potentially have a total supply\nof max `uint256`.\n\nDocumentation for the MakerDAO flash mint module can be found\n[here](https://docs.makerdao.com/smart-contract-modules/flash-mint-module).\n\n## Tokens with Blocklists\n\nSome tokens (e.g. `USDC`, `USDT`) have a contract level admin controlled address blocklist. If an\naddress is blocked, then transfers to and from that address are forbidden.\n\nMalicious or compromised token owners can trap funds in a contract by adding the contract address to\nthe blocklist. This could potentially be the result of regulatory action against the contract\nitself, against a single user of the contract (e.g. a Uniswap LP), or could also be a part of an\nextortion attempt against users of the blocked contract.\n\n*example*: [BlockList.sol](./src/BlockList.sol)\n\n## Pausable Tokens\n\nSome tokens can be paused by an admin (e.g. `BNB`, `ZIL`).\n\nSimilary to the blocklist issue above, an admin controlled pause feature opens users\nof the token to risk from a malicious or compromised token owner.\n\n*example*: [Pausable.sol](./src/Pausable.sol)\n\n## Approval Race Protections\n\nSome tokens (e.g. `USDT`, `KNC`) do not allow approving an amount `M \u003e 0` when an existing amount\n`N \u003e 0` is already approved. This is to protect from an ERC20 attack vector described\n[here](https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/edit#heading=h.b32yfk54vyg9).\n\n[This PR](https://github.com/Uniswap/uniswap-v2-periphery/pull/26#issuecomment-647543138) shows some\nin the wild problems caused by this issue.\n\n*example*: [Approval.sol](./src/Approval.sol)\n\n## Revert on Approval To Zero Address\n\nSome tokens (e.g. OpenZeppelin) will revert if trying to approve the zero address to spend tokens\n(i.e. a call to `approve(address(0), amt)`).\n\nIntegrators may need to add special cases to handle this logic if working with such a token.\n\n*example*: [ApprovalToZeroAddress.sol](./src/ApprovalToZeroAddress.sol)\n\n## Revert on Zero Value Approvals\n\nSome tokens (e.g. `BNB`) revert when approving a zero value amount (i.e. a call to `approve(address, 0)`).\n\nIntegrators may need to add special cases to handle this logic if working with such a token.\n\n*example*: [ApprovalWithZeroValue.sol](./src/ApprovalWithZeroValue.sol)\n\n## Revert on Zero Value Transfers\n\nSome tokens (e.g. `LEND`) revert when transferring a zero value amount.\n\n*example*: [RevertZero.sol](./src/RevertZero.sol)\n\n## Multiple Token Addresses\n\nSome proxied tokens have multiple addresses. \nAs an example consider the following snippet. `rescueFunds` is intended to allow the contract owner\nto return non pool tokens that were accidentally sent to the contract. However, it assumes a single\naddress per token and so would allow the owner to steal all funds in the pool.\n\n```solidity\nmapping isPoolToken(address =\u003e bool);\nconstructor(address tokenA, address tokenB) public {\n  isPoolToken[tokenA] = true;\n  isPoolToken[tokenB] = true;\n}\nfunction rescueFunds(address token, uint amount) external nonReentrant onlyOwner {\n    require(!isPoolToken[token], \"access denied\");\n    token.transfer(msg.sender, amount);\n}\n```\n\n*example*: [Proxied.sol](./src/Proxied.sol)\n\n## Low Decimals\n\nSome tokens have low decimals (e.g. `USDC` has 6). Even more extreme, some tokens like [Gemini USD](https://etherscan.io/token/0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd?a=0x5f65f7b609678448494De4C87521CdF6cEf1e932) only have 2 decimals.\n\nThis may result in larger than expected precision loss.\n\n*example*: [LowDecimals.sol](./src/LowDecimals.sol)\n\n## High Decimals\n\nSome tokens have more than 18 decimals (e.g. `YAM-V2` has 24).\n\nThis may trigger unexpected reverts due to overflow, posing a liveness risk to the contract.\n\n*example*: [HighDecimals.sol](./src/HighDecimals.sol)\n\n## `transferFrom` with `src == msg.sender`\n\nSome token implementations (e.g. `DSToken`) will not attempt to decrease the caller's allowance if\nthe sender is the same as the caller. This gives `transferFrom` the same semantics as `transfer` in\nthis case. Other implementations (e.g. OpenZeppelin, Uniswap-v2) will attempt to decrease the\ncaller's allowance from the sender in `transferFrom` even if the caller and the sender are the same\naddress, giving `transfer(dst, amt)` and `transferFrom(address(this), dst, amt)` a different\nsemantics in this case.\n\n*examples*:\n\nExamples of both semantics are provided:\n\n- [ERC20.sol](./src/ERC20.sol): does not attempt to decrease allowance\n- [TransferFromSelf.sol](./src/TransferFromSelf.sol): always attempts to decrease the allowance\n\n## Non `string` metadata\n\nSome tokens (e.g.\n[`MKR`](https://etherscan.io/address/0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2#code)) have metadata\nfields (`name` / `symbol`) encoded as `bytes32` instead of the `string` prescribed by the ERC20\nspecification.\n\nThis may cause issues when trying to consume metadata from these tokens.\n\n*example*: [Bytes32Metadata.sol](./src/Bytes32Metadata.sol)\n\n## Revert on Transfer to the Zero Address\n\nSome tokens (e.g. openzeppelin) revert when attempting to transfer to `address(0)`.\n\nThis may break systems that expect to be able to burn tokens by transferring them to `address(0)`.\n\n*example*: [RevertToZero.sol](./src/RevertToZero.sol)\n\n## No Revert on Failure\n\nSome tokens do not revert on failure, but instead return `false` (e.g.\n[ZRX](https://etherscan.io/address/0xe41d2489571d322189246dafa5ebde1f4699f498#code), [EURS](https://etherscan.io/token/0xdb25f211ab05b1c97d595516f45794528a807ad8#code)).\n\nWhile this is technically compliant with the ERC20 standard, it goes against common solidity coding\npractices and may be overlooked by developers who forget to wrap their calls to `transfer` in a\n`require`.\n\n*example*: [NoRevert.sol](./src/NoRevert.sol)\n\n## Revert on Large Approvals \u0026 Transfers\n\nSome tokens (e.g. `UNI`, `COMP`) revert if the value passed to `approve` or `transfer` is larger than `uint96`.\n\nBoth of the above tokens have special case logic in `approve` that sets `allowance` to `type(uint96).max`\nif the approval amount is `uint256(-1)`, which may cause issues with systems that expect the value\npassed to `approve` to be reflected in the `allowances` mapping.\n\n*example*: [Uint96.sol](./src/Uint96.sol)\n\n## Code Injection Via Token Name\n\nSome malicious tokens have been observed to include malicious javascript in their `name` attribute,\nallowing attackers to extract private keys from users who choose to interact with these tokens via\nvulnerable frontends.\n\nThis has been used to exploit etherdelta users in the wild ([reference](https://hackernoon.com/how-one-hacker-stole-thousands-of-dollars-worth-of-cryptocurrency-with-a-classic-code-injection-a3aba5d2bff0)).\n\n## Unusual Permit Function\n\nSome tokens ([DAI, RAI, GLM, STAKE, CHAI, HAKKA, USDFL, HNY](https://github.com/yashnaman/tokensWithPermitFunctionList/blob/master/hasDAILikePermitFunctionTokenList.json)) have a `permit()` implementation that does not follow [EIP2612](https://eips.ethereum.org/EIPS/eip-2612). Tokens that do not support permit may not revert, which [could lead to the execution of later lines of code in unexpected scenarios](https://media.dedaub.com/phantom-functions-and-the-billion-dollar-no-op-c56f062ae49f). [Uniswap's Permit2](https://github.com/Uniswap/permit2) may provide a more compatible alternative.\n\n*example*: [DaiPermit.sol](./src/DaiPermit.sol)\n\n## Transfer of less than `amount`\n\nSome tokens (e.g., `cUSDCv3`) contain a special case for `amount == type(uint256).max` in their transfer functions that results in only the user's balance being transferred. \n\nThis may cause issues with systems that transfer a user-supplied `amount` to their contract and then credit the user with the same value in storage (e.g., Vault-type systems) without checking the amount that has actually been transferred.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-xo%2Fweird-erc20","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fd-xo%2Fweird-erc20","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fd-xo%2Fweird-erc20/lists"}