FU is a shitpost of a token. It was designed and implemented purely for the amusement of its authors and its participants. It is, in a sense, blockchain art. By holding FU, you are participating in this art project. The authors hope that it brings you joy, because it won't bring you wealth. As a secondary utility, because FU is such a terrible token, it's useful to test against by dApp developers.
While FU is ownerless, decentralized, censorship-resistant, immutable, liquidity-locked, and great pains have been taken to ensure that it is bug-free, its authors make no guarantees about its correctness. See LICENSE.txt for full IP terms. See the Legal section for disclaimers.
FU is designed to be maximally vexing for developers to integrate, while still being technically correct.
- Unreasonably high decimals (35) [why not 36? fuck you.]
- Reflection (both tax and rebase)
- Tax rate changes depending on the phase of the moon
- Randomly reverts or returns
false
to signal failure - Randomly returns nothing or returns
true
to signal success- Ok, this is not technically ERC20 compliant, but I'm going to say it's "close enough" given that there are some important "ERC20" tokens that exhibit the same behavior
symbol
depends on the identity of the caller- The shares-to-tokens ratio depends on the address of the holder
- Consequently,
totalSupply
is merely an upper bound on the sum of allbalanceOf(...)
- Balances are not comparable among addresses
- Consequently,
- Anti-whale
- The anti-whale doesn't prohibit transfers, it just turns transfers above the
limit into a
deliver
- The anti-whale doesn't prohibit transfers, it just turns transfers above the
limit into a
- Emits extraneous
Transfer
events on each call- This breaks some off-chain data pipelines
- Consumes a random, hard-to-predict amount of gas on each call
- This makes
eth_estimateGas
unreliable
- This makes
- A base slot inspired by ERC7201 (but not directly compatible with it) to make slot detection more difficult
FU is designed to still be almost completely compliant with ERC20 as written (ERC20 doesn't allow tokens to return nothing to signal success). However, to make things a little more interesting, there are some additional restrictions the authors adopted during development beyond what ERC20 literally requires.
- Calls to
transfer
ortransferFrom
reduce the balance of the caller/from
by exactly the specified amount - Calls to
transfer
ortransferFrom
increase the balance ofto
by a value that lies in the range of reasonable interpretations of how it should be calculated- Lower bound: compute the tax amount exactly, round it up, then deduct it from the specified amount
- Upper bound: exactly compute the specified amount minus the tax, round it up
"Normal" reflection tokens do not have these properties, and the authors adopted these restrictions primarily to demonstrate mastery of the required numerical programming techniques. This creates some interesting game-theoretic interplay where it sometimes becomes advantageous to store tokens in multiple addresses, so that the holder benefits from the tax applied to their own incoming and outgoing transfers.
FU is a full-featured token, supporting the following extensions to the ERC20 standard (with metadata)
- ERC2612 -- EIP-20 approvals via EIP-712 secp256k1 signatures
- ERC5267 -- Retrieval of EIP-712 domain
- ERC5805 -- Voting with delegation
- ERC6093 -- Custom errors for commonly-used tokens
- ERC6372 -- Contract clock
- ERC7674 -- Temporary Approval Extension for ERC-20
The allowance from each account to Permit2
(0x000000000022D473030F116dDEE9F6B43aC78BA3
) is always infinity
(type(uint256).max
).
tax()(uint256)
(view)image()(string)
(view)whaleLimit(address)(uint256)
(view)burn(uint256)(bool)
burnFrom(address,uint256)(bool)
deliver(uint256)(bool)
deliverFrom(address,uint256)(bool)
Note that the following non-standard extensions to ERC20 are intended to be used
to interface with an instance of OpenZeppelin's GovernorVotesQuorumFraction
contract BUT the selectors that implement reading the past "totalSupply
"
are deliberately incompatible with that contract (without a simple
modification). This is because the current total voting supply is (necessarily)
not totalSupply
, therefore the signature getPastTotalSupply
is
misleading. Additionally, FU implements these functions in a way that returns
the actively delegated voting power, not the hypothetical voting power if all
tokens were delegated.
getTotalVotes()(uint256)
(view)getPastTotalVotes(uint256)(uint256)
(view)
FU is otherwise compatible with the OpenZeppelin Governor suite. The authors recommend a Governor that inherits from the following OpenZeppelin contracts (version 5.1.0):
Governor
GovernorSettings
GovernorCountingSimple
GovernorVotes
GovernorVotesQuorumFraction
(with the aforementioned modifications)GovernorTimelockControl
GovernorPreventLateQuorum
Remember that FU uses block.timestamp
for durations, with a quantum of 1 day
(midnight). The authors recommend setting the voting delay to 2 days, the voting
period to 1 week, the vote extension period to 4 days, the quorum fraction to
33%, and the timelock min delay to 2 weeks.
A wrapped version of the FU token is not provided. It could be easily
implemented as an ERC4626 vault. One should be careful, however, not to trip
over the anti-whale provision of FU. The relative increase of the
balanceOf(...)
of the vault is a reliable indicator of the value received
during minting (and consequently the amount of shares that should be
minted). However, because the underlying token is a transfer tax token, the
amount
passed to transfer
or transferFrom
is not a reliable indicator of
the balance increase of the to
. Additionally, wFU must be a tax token to
reflect the transfer tax of the underlying token. For that reason, transfer
and transferFrom
should implement a pattern like:
address internal constant _DEAD = 0xdeaDDeADDEaDdeaDdEAddEADDEAdDeadDEADDEaD;
constructor() {
assert(uint256(uint160(address(this))) / Settings.ADDRESS_DIVISOR == Settings.CRAZY_BALANCE_BASIS);
uint256 balance = asset.balanceOf(address(this));
require(balance >= 1 ether);
balanceOf[_DEAD] = balance;
totalSupply = balance;
emit Transfer(address(0), _DEAD, balance);
}
function _transfer(address from, address to, uint256 amount) internal {
uint256 balance = asset.balanceOf(address(this));
uint512 n = alloc().omul(balance, amount * asset.tax());
uint256 d = totalSupply * BasisPoints.unwrap(BasisPoints.BASIS);
uint256 taxAmountFu = n.div(d);
taxAmountFu = taxAmountFu.unsafeInc(tmp().omul(taxAmountFu, d) < n);
n.omul(taxAmountFu, totalSupply);
d = balance;
uint256 taxAmount = n.div(d);
taxAmount = taxAmount.unsafeInc(tmp().omul(taxAmount, d) < n);
balanceOf[from] -= amount; // underflow indicates insufficient balance
unchecked {
totalSupply -= taxAmount;
amount -= taxAmount;
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
emit Transfer(from, address(0), taxAmount);
asset.deliver(taxAmountFu);
}
Exactly how to fully generalize these concepts to an ERC4626 compatible tokenized vault while correctly handing rounding error and avoiding inflation attacks is left as an exercise for the implementer.
FU was developed using the Foundry framework with Slither for static analysis and medusa as a coverage-guided complement to Foundry's fuzzer. FU also uses Dedaub's analysis suite as a more powerful complement to Slither.
The differential fuzz tests and invariant/property tests in this repository take quite a long time to run.
Install analysis tools from Crytic (Trail of Bits)
python3 -m pip install --user --upgrade crytic-compile
python3 -m pip install --user --upgrade slither-analyzer
Install Dedaub srcup
python3 -m pip install --user --upgrade git+https://github.com/Dedaub/srcup#egg=srcup
forge test -vvv --fuzz-seed "$(python3 -c 'import secrets; print(secrets.randbelow(2**53))')"
./medusa fuzz # or use your system `medusa`
slither . # slither has a heisenbug; just run it repeatedly until it doesn't crash
srcup --api-key "$(< ~/.config/dedaub/credentials)" --name fu --framework Foundry .
git reset --hard HEAD
forge clean
git clean -fdx
# substitute actual values
declare -r salt="$(cast hash-zero)"
# add `--broadcast` when ready
FOUNDRY_PROFILE=deploy forge script -vvvv --slow --no-storage-caching --isolate --rpc-url 'http://127.0.0.1:8545' --sig 'run(bytes32)' script/DeployFU.s.sol "$salt"
If you're looking for a hot new coin to ape that's gonna give you a good pump, then this is not the coin for you. The authors are not pumpooors (they're borderline incompetent with HTML/JS/CSS), and neither are they going to put any further development, management, or promotional effort into this token. If you want to tell your friends about it, the authors can't stop you, but you're probably going to lose them money by suggesting this to them.
This is strictly an art project. FU has no inherent value. The pricing is set by an AMM with fully locked liquidity; the authors do not market make, entreat professional market makers, or stabilize/increase the price. Owning FU does not entitle you to some future income stream. There is no treasury, community fund, marketing fund, or other source of funds for ongoing operations.
Owning FU is like owning a participation trophy. The trophy is for being determined enough to actually make a swap happen in spite of all the roadblocks set in the way.
For developers, if your dApp works with FU, then it will probably work with every other token on earth. Perhaps there is some utility in having a maximally-badly-behaved ERC20 token for testing purposes.
If you want to be airdropped some FU, simply submit a PR adding yourself to
airdrop.json
. There is a recommended donation/gratuity of
0.1 ETH that can be paid to
deployer.duncancmt.eth
(aka 0x3D87e294ba9e29d2B5a557a45afCb0D052a13ea6
). This also serves as a
proof-of-humanity/anti-spam mechanism. All donations will be locked forever as
liquidity. There are no eligibility criteria. Whether or not you receive an
airdrop is entirely contingent on the capricious and arbitrary whims of the
authors.