diff --git a/.gas-snapshot b/.gas-snapshot index f32f573..d56cb82 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,4 +1,4 @@ -RagequitterTest:testDeploy() (gas: 1762199) +RagequitterTest:testDeploy() (gas: 1795880) RagequitterTest:testInstall() (gas: 34130) RagequitterTest:testMint() (gas: 79585) RagequitterTest:testRagequit() (gas: 170325) diff --git a/README.md b/README.md index 4d0e917..865a3dd 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ *Ragequit* redemption for all accounts. Developed and deployed as the [Ragequitter](./src/Ragequitter.sol) solidity singleton. +V1 Deployment: [0x0000000000008743D388E5279B2A9EF87A3115Ae](https://arbiscan.io/address/0x0000000000008743d388e5279b2a9ef87a3115ae#code) + ## How it works Let's say you have a Gnosis Safe or smart account or DAO treasury. And you want to make it so people can take tokens out of your account without a proposal by burning other tokens ("redemption", and those other tokens, "loot"). The classic use case of this is the ["Moloch DAO ragequit"](https://github.com/MolochVentures/moloch/blob/master/contracts/Moloch.sol#L528), which is a game theory mechanism that serves trustless crowdfunding ("tribute") by guaranteeing contributors that they can always get their share of tribute back if they burn loot. diff --git a/docs/src/README.md b/docs/src/README.md index 4d0e917..865a3dd 100644 --- a/docs/src/README.md +++ b/docs/src/README.md @@ -2,6 +2,8 @@ *Ragequit* redemption for all accounts. Developed and deployed as the [Ragequitter](./src/Ragequitter.sol) solidity singleton. +V1 Deployment: [0x0000000000008743D388E5279B2A9EF87A3115Ae](https://arbiscan.io/address/0x0000000000008743d388e5279b2a9ef87a3115ae#code) + ## How it works Let's say you have a Gnosis Safe or smart account or DAO treasury. And you want to make it so people can take tokens out of your account without a proposal by burning other tokens ("redemption", and those other tokens, "loot"). The classic use case of this is the ["Moloch DAO ragequit"](https://github.com/MolochVentures/moloch/blob/master/contracts/Moloch.sol#L528), which is a game theory mechanism that serves trustless crowdfunding ("tribute") by guaranteeing contributors that they can always get their share of tribute back if they burn loot. diff --git a/docs/src/src/Ragequitter.sol/contract.Ragequitter.md b/docs/src/src/Ragequitter.sol/contract.Ragequitter.md index 5a83923..8812dd8 100644 --- a/docs/src/src/Ragequitter.sol/contract.Ragequitter.md +++ b/docs/src/src/Ragequitter.sol/contract.Ragequitter.md @@ -1,5 +1,5 @@ # Ragequitter -[Git Source](https://github.com/Moloch-Mystics/ragequit/blob/ed98f5886956972c8e4422a029cd6053d14f537c/src/Ragequitter.sol) +[Git Source](https://github.com/Moloch-Mystics/ragequit/blob/8d443b27797d45370cd001aa46ede3a2766571c4/src/Ragequitter.sol) **Inherits:** ERC6909 @@ -129,7 +129,7 @@ If no `tribute` is set, then function will revert on `safeTransferFrom`.* ```solidity -function contribute(address account, uint256 amount) public payable virtual; +function contribute(address account, uint96 amount) public payable virtual; ``` ### install diff --git a/docs/src/src/Ragequitter.sol/interface.IAuth.md b/docs/src/src/Ragequitter.sol/interface.IAuth.md index 326e494..42392fd 100644 --- a/docs/src/src/Ragequitter.sol/interface.IAuth.md +++ b/docs/src/src/Ragequitter.sol/interface.IAuth.md @@ -1,5 +1,5 @@ # IAuth -[Git Source](https://github.com/Moloch-Mystics/ragequit/blob/ed98f5886956972c8e4422a029cd6053d14f537c/src/Ragequitter.sol) +[Git Source](https://github.com/Moloch-Mystics/ragequit/blob/8d443b27797d45370cd001aa46ede3a2766571c4/src/Ragequitter.sol) Simple authority interface for contracts. diff --git a/etherscan.json b/etherscan.json new file mode 100644 index 0000000..4f56548 --- /dev/null +++ b/etherscan.json @@ -0,0 +1 @@ +{"language":"Solidity","sources":{"src/Ragequitter.sol":{"content":"// ᗪᗩGOᑎ 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭 𒀭\n// SPDX-License-Identifier: AGPL-3.0-only\npragma solidity ^0.8.19;\n\nimport {ERC6909} from \"@solady/src/tokens/ERC6909.sol\";\n\n/// @notice Simple ragequit singleton with ERC6909 accounting. Version 1.\ncontract Ragequitter is ERC6909 {\n /// ======================= CUSTOM ERRORS ======================= ///\n\n /// @dev Invalid time window for ragequit.\n error InvalidTime();\n\n /// @dev Out-of-order redemption assets.\n error InvalidAssetOrder();\n\n /// @dev Overflow or division by zero.\n error MulDivFailed();\n\n /// @dev ERC20 `transferFrom` failed.\n error TransferFromFailed();\n\n /// @dev ETH transfer failed.\n error ETHTransferFailed();\n\n /// =========================== EVENTS =========================== ///\n\n /// @dev Logs new account loot metadata.\n event URI(string metadata, uint256 indexed id);\n\n /// @dev Logs new account authority contract.\n event AuthSet(address indexed account, IAuth authority);\n\n /// @dev Logs new account contribution asset setting.\n event TributeSet(address indexed account, address tribute);\n\n /// @dev Logs new account ragequit time validity setting.\n event TimeValiditySet(address indexed account, uint48 validAfter, uint48 validUntil);\n\n /// ========================== STRUCTS ========================== ///\n\n /// @dev The account loot shares metadata struct.\n struct Metadata {\n string name;\n string symbol;\n string tokenURI;\n IAuth authority;\n uint96 totalSupply;\n }\n\n /// @dev The account loot shares ownership struct.\n struct Ownership {\n address owner;\n uint96 shares;\n }\n\n /// @dev The account loot shares settings struct.\n struct Settings {\n address tribute;\n uint48 validAfter;\n uint48 validUntil;\n }\n\n /// ========================= CONSTANTS ========================= ///\n\n /// @dev The conventional ERC7528 ETH address.\n address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;\n\n /// ========================== STORAGE ========================== ///\n\n /// @dev Stores mapping of metadata settings to account token IDs.\n /// note: IDs are unique to addresses (`uint256(uint160(account))`).\n mapping(uint256 id => Metadata) internal _metadata;\n\n /// @dev Stores mapping of ragequit settings to accounts.\n mapping(address account => Settings) internal _settings;\n\n /// ================= ERC6909 METADATA & SUPPLY ================= ///\n\n /// @dev Returns the name for token `id` using this contract.\n function name(uint256 id) public view virtual override(ERC6909) returns (string memory) {\n return _metadata[id].name;\n }\n\n /// @dev Returns the symbol for token `id` using this contract.\n function symbol(uint256 id) public view virtual override(ERC6909) returns (string memory) {\n return _metadata[id].symbol;\n }\n\n /// @dev Returns the URI for token `id` using this contract.\n function tokenURI(uint256 id) public view virtual override(ERC6909) returns (string memory) {\n return _metadata[id].tokenURI;\n }\n\n /// @dev Returns the total supply for token `id` using this contract.\n function totalSupply(uint256 id) public view virtual returns (uint256) {\n return _metadata[id].totalSupply;\n }\n\n /// ========================== RAGEQUIT ========================== ///\n\n /// @dev Ragequits `shares` of `account` loot for their current fair share of pooled `assets`.\n function ragequit(address account, uint96 shares, address[] calldata assets) public virtual {\n Settings storage setting = _settings[account];\n\n if (block.timestamp < setting.validAfter) revert InvalidTime();\n if (block.timestamp > setting.validUntil) revert InvalidTime();\n if (assets.length == 0) revert InvalidAssetOrder();\n\n uint256 id = uint256(uint160(account));\n uint256 supply = _metadata[id].totalSupply;\n unchecked {\n _metadata[id].totalSupply -= shares;\n }\n _burn(msg.sender, id, shares);\n\n address asset;\n address prev;\n uint256 share;\n\n for (uint256 i; i != assets.length; ++i) {\n asset = assets[i];\n if (asset <= prev) revert InvalidAssetOrder();\n prev = asset;\n share = _mulDiv(shares, _balanceOf(asset, account), supply);\n if (share != 0) _safeTransferFrom(asset, account, msg.sender, share);\n }\n }\n\n /// @dev Returns `floor(x * y / d)`.\n /// Reverts if `x * y` overflows, or `d` is zero.\n function _mulDiv(uint256 x, uint256 y, uint256 d) internal pure virtual returns (uint256 z) {\n assembly (\"memory-safe\") {\n // Equivalent to require(d != 0 && (y == 0 || x <= type(uint256).max / y))\n if iszero(mul(d, iszero(mul(y, gt(x, div(not(0), y)))))) {\n mstore(0x00, 0xad251c27) // `MulDivFailed()`.\n revert(0x1c, 0x04)\n }\n z := div(mul(x, y), d)\n }\n }\n\n /// ============================ LOOT ============================ ///\n\n /// @dev Mints loot shares for an owner of the caller account.\n function mint(address owner, uint96 shares) public virtual {\n uint256 id = uint256(uint160(msg.sender));\n _metadata[id].totalSupply += shares;\n _mint(owner, id, shares);\n }\n\n /// @dev Burns loot shares from an owner of the caller account.\n function burn(address owner, uint96 shares) public virtual {\n uint256 id = uint256(uint160(msg.sender));\n unchecked {\n _metadata[id].totalSupply -= shares;\n }\n _burn(owner, id, shares);\n }\n\n /// ========================== TRIBUTE ========================== ///\n\n /// @dev Mints loot shares in exchange for tribute `amount` to an `account`.\n /// If no `tribute` is set, then function will revert on `safeTransferFrom`.\n function contribute(address account, uint96 amount) public payable virtual {\n address tribute = _settings[account].tribute;\n if (tribute == ETH) _safeTransferETH(account, amount);\n else _safeTransferFrom(tribute, msg.sender, account, amount);\n uint256 id = uint256(uint160(account));\n _metadata[id].totalSupply += amount;\n _mint(msg.sender, id, amount);\n }\n\n /// ======================== INSTALLATION ======================== ///\n\n /// @dev Initializes ragequit settings for the caller account.\n function install(Ownership[] calldata owners, Settings calldata setting, Metadata calldata meta)\n public\n virtual\n {\n uint256 id = uint256(uint160(msg.sender));\n if (owners.length != 0) {\n uint96 supply;\n for (uint256 i; i != owners.length; ++i) {\n supply += owners[i].shares;\n _mint(owners[i].owner, id, owners[i].shares);\n }\n _metadata[id].totalSupply += supply;\n }\n if (bytes(meta.name).length != 0) {\n _metadata[id].name = meta.name;\n _metadata[id].symbol = meta.symbol;\n }\n if (bytes(meta.tokenURI).length != 0) {\n emit URI((_metadata[id].tokenURI = meta.tokenURI), id);\n }\n if (meta.authority != IAuth(address(0))) {\n emit AuthSet(msg.sender, (_metadata[id].authority = meta.authority));\n }\n _settings[msg.sender] = Settings(setting.tribute, setting.validAfter, setting.validUntil);\n emit TimeValiditySet(msg.sender, setting.validAfter, setting.validUntil);\n emit TributeSet(msg.sender, setting.tribute);\n }\n\n /// ==================== SETTINGS & METADATA ==================== ///\n\n /// @dev Returns the account metadata.\n function getMetadata(address account)\n public\n view\n virtual\n returns (string memory, string memory, string memory, IAuth)\n {\n Metadata storage meta = _metadata[uint256(uint160(account))];\n return (meta.name, meta.symbol, meta.tokenURI, meta.authority);\n }\n\n /// @dev Returns the account tribute and ragequit time validity settings.\n function getSettings(address account) public view virtual returns (address, uint48, uint48) {\n Settings storage setting = _settings[account];\n return (setting.tribute, setting.validAfter, setting.validUntil);\n }\n\n /// @dev Sets new authority contract for the caller account.\n function setAuth(IAuth authority) public virtual {\n emit AuthSet(msg.sender, (_metadata[uint256(uint160(msg.sender))].authority = authority));\n }\n\n /// @dev Sets account and loot token URI `metadata`.\n function setURI(string calldata metadata) public virtual {\n uint256 id = uint256(uint160(msg.sender));\n emit URI((_metadata[id].tokenURI = metadata), id);\n }\n\n /// @dev Sets account ragequit time validity (or 'time window').\n function setTimeValidity(uint48 validAfter, uint48 validUntil) public virtual {\n emit TimeValiditySet(\n msg.sender,\n _settings[msg.sender].validAfter = validAfter,\n _settings[msg.sender].validUntil = validUntil\n );\n }\n\n /// @dev Sets account contribution asset (tribute).\n function setTribute(address tribute) public virtual {\n emit TributeSet(msg.sender, _settings[msg.sender].tribute = tribute);\n }\n\n /// =================== EXTERNAL ASSET HELPERS =================== ///\n\n /// @dev Returns the `amount` of ERC20 `token` owned by `account`.\n /// Returns zero if the `token` does not exist.\n function _balanceOf(address token, address account)\n internal\n view\n virtual\n returns (uint256 amount)\n {\n assembly (\"memory-safe\") {\n mstore(0x14, account) // Store the `account` argument.\n mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.\n amount :=\n mul( // The arguments of `mul` are evaluated from right to left.\n mload(0x20),\n and( // The arguments of `and` are evaluated from right to left.\n gt(returndatasize(), 0x1f), // At least 32 bytes returned.\n staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)\n )\n )\n }\n }\n\n /// @dev Sends `amount` (in wei) ETH to `to`.\n function _safeTransferETH(address to, uint256 amount) internal virtual {\n assembly (\"memory-safe\") {\n if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {\n mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.\n revert(0x1c, 0x04)\n }\n }\n }\n\n /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.\n function _safeTransferFrom(address token, address from, address to, uint256 amount)\n internal\n virtual\n {\n assembly (\"memory-safe\") {\n let m := mload(0x40) // Cache the free memory pointer.\n mstore(0x60, amount) // Store the `amount` argument.\n mstore(0x40, to) // Store the `to` argument.\n mstore(0x2c, shl(96, from)) // Store the `from` argument.\n mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.\n // Perform the transfer, reverting upon failure.\n if iszero(\n and( // The arguments of `and` are evaluated from right to left.\n or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.\n call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)\n )\n ) {\n mstore(0x00, 0x7939f424) // `TransferFromFailed()`.\n revert(0x1c, 0x04)\n }\n mstore(0x60, 0) // Restore the zero slot to zero.\n mstore(0x40, m) // Restore the free memory pointer.\n }\n }\n\n /// ========================= OVERRIDES ========================= ///\n\n /// @dev Hook that is called before any transfer of tokens.\n /// This includes minting and burning. Also requests authority for token transfers.\n function _beforeTokenTransfer(address from, address to, uint256 id, uint256 amount)\n internal\n virtual\n override(ERC6909)\n {\n IAuth authority = _metadata[id].authority;\n if (authority != IAuth(address(0))) authority.validateTransfer(from, to, id, amount);\n }\n}\n\n/// @notice Simple authority interface for contracts.\ninterface IAuth {\n function validateTransfer(address, address, uint256, uint256)\n external\n payable\n returns (uint256);\n}\n"},"lib/accounts/lib/solady/src/tokens/ERC6909.sol":{"content":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.4;\n\n/// @notice Simple EIP-6909 implementation.\n/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC6909.sol)\n///\n/// @dev Note:\n/// The ERC6909 standard allows minting and transferring to and from the zero address,\n/// minting and transferring zero tokens, as well as self-approvals.\n/// For performance, this implementation WILL NOT revert for such actions.\n/// Please add any checks with overrides if desired.\n///\n/// If you are overriding:\n/// - Make sure all variables written to storage are properly cleaned\n// (e.g. the bool value for `isOperator` MUST be either 1 or 0 under the hood).\n/// - Check that the overridden function is actually used in the function you want to\n/// change the behavior of. Much of the code has been manually inlined for performance.\nabstract contract ERC6909 {\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* CUSTOM ERRORS */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n /// @dev Insufficient balance.\n error InsufficientBalance();\n\n /// @dev Insufficient permission to perform the action.\n error InsufficientPermission();\n\n /// @dev The balance has overflowed.\n error BalanceOverflow();\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* EVENTS */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n /// @dev Emitted when `by` transfers `amount` of token `id` from `from` to `to`.\n event Transfer(\n address by, address indexed from, address indexed to, uint256 indexed id, uint256 amount\n );\n\n /// @dev Emitted when `owner` enables or disables `operator` to manage all of their tokens.\n event OperatorSet(address indexed owner, address indexed operator, bool approved);\n\n /// @dev Emitted when `owner` approves `spender` to use `amount` of `id` token.\n event Approval(\n address indexed owner, address indexed spender, uint256 indexed id, uint256 amount\n );\n\n /// @dev `keccak256(bytes(\"Transfer(address,address,address,uint256,uint256)\"))`.\n uint256 private constant _TRANSFER_EVENT_SIGNATURE =\n 0x1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859;\n\n /// @dev `keccak256(bytes(\"OperatorSet(address,address,bool)\"))`.\n uint256 private constant _OPERATOR_SET_EVENT_SIGNATURE =\n 0xceb576d9f15e4e200fdb5096d64d5dfd667e16def20c1eefd14256d8e3faa267;\n\n /// @dev `keccak256(bytes(\"Approval(address,address,uint256,uint256)\"))`.\n uint256 private constant _APPROVAL_EVENT_SIGNATURE =\n 0xb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a7;\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* STORAGE */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n /// @dev The `ownerSlotSeed` of a given owner is given by.\n /// ```\n /// let ownerSlotSeed := or(_ERC6909_MASTER_SLOT_SEED, shl(96, owner))\n /// ```\n ///\n /// The balance slot of `owner` is given by.\n /// ```\n /// mstore(0x20, ownerSlotSeed)\n /// mstore(0x00, id)\n /// let balanceSlot := keccak256(0x00, 0x40)\n /// ```\n ///\n /// The operator approval slot of `owner` is given by.\n /// ```\n /// mstore(0x20, ownerSlotSeed)\n /// mstore(0x00, operator)\n /// let operatorApprovalSlot := keccak256(0x0c, 0x34)\n /// ```\n ///\n /// The allowance slot of (`owner`, `spender`, `id`) is given by:\n /// ```\n /// mstore(0x34, ownerSlotSeed)\n /// mstore(0x14, spender)\n /// mstore(0x00, id)\n /// let allowanceSlot := keccak256(0x00, 0x54)\n /// ```\n uint256 private constant _ERC6909_MASTER_SLOT_SEED = 0xedcaa89a82293940;\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* ERC6909 METADATA */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n /// @dev Returns the name for token `id`.\n function name(uint256 id) public view virtual returns (string memory);\n\n /// @dev Returns the symbol for token `id`.\n function symbol(uint256 id) public view virtual returns (string memory);\n\n /// @dev Returns the number of decimals for token `id`.\n /// Returns 18 by default.\n /// Please override this function if you need to return a custom value.\n function decimals(uint256 id) public view virtual returns (uint8) {\n id = id; // Silence compiler warning.\n return 18;\n }\n\n /// @dev Returns the Uniform Resource Identifier (URI) for token `id`.\n function tokenURI(uint256 id) public view virtual returns (string memory);\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* ERC6909 */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n /// @dev Returns the amount of token `id` owned by `owner`.\n function balanceOf(address owner, uint256 id) public view virtual returns (uint256 amount) {\n /// @solidity memory-safe-assembly\n assembly {\n mstore(0x20, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x14, owner)\n mstore(0x00, id)\n amount := sload(keccak256(0x00, 0x40))\n }\n }\n\n /// @dev Returns the amount of token `id` that `spender` can spend on behalf of `owner`.\n function allowance(address owner, address spender, uint256 id)\n public\n view\n virtual\n returns (uint256 amount)\n {\n /// @solidity memory-safe-assembly\n assembly {\n mstore(0x34, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x28, owner)\n mstore(0x14, spender)\n mstore(0x00, id)\n amount := sload(keccak256(0x00, 0x54))\n // Restore the part of the free memory pointer that has been overwritten.\n mstore(0x34, 0x00)\n }\n }\n\n /// @dev Checks if a `spender` is approved by `owner` to manage all of their tokens.\n function isOperator(address owner, address spender) public view virtual returns (bool status) {\n /// @solidity memory-safe-assembly\n assembly {\n mstore(0x20, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x14, owner)\n mstore(0x00, spender)\n status := sload(keccak256(0x0c, 0x34))\n }\n }\n\n /// @dev Transfers `amount` of token `id` from the caller to `to`.\n ///\n /// Requirements:\n /// - caller must at least have `amount`.\n ///\n /// Emits a {Transfer} event.\n function transfer(address to, uint256 id, uint256 amount)\n public\n payable\n virtual\n returns (bool)\n {\n _beforeTokenTransfer(msg.sender, to, id, amount);\n /// @solidity memory-safe-assembly\n assembly {\n /// Compute the balance slot and load its value.\n mstore(0x20, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x14, caller())\n mstore(0x00, id)\n let fromBalanceSlot := keccak256(0x00, 0x40)\n let fromBalance := sload(fromBalanceSlot)\n // Revert if insufficient balance.\n if gt(amount, fromBalance) {\n mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.\n revert(0x1c, 0x04)\n }\n // Subtract and store the updated balance.\n sstore(fromBalanceSlot, sub(fromBalance, amount))\n // Compute the balance slot of `to`.\n mstore(0x14, to)\n mstore(0x00, id)\n let toBalanceSlot := keccak256(0x00, 0x40)\n let toBalanceBefore := sload(toBalanceSlot)\n let toBalanceAfter := add(toBalanceBefore, amount)\n // Revert if the balance overflows.\n if lt(toBalanceAfter, toBalanceBefore) {\n mstore(0x00, 0x89560ca1) // `BalanceOverflow()`.\n revert(0x1c, 0x04)\n }\n // Store the updated balance of `to`.\n sstore(toBalanceSlot, toBalanceAfter)\n // Emit the {Transfer} event.\n mstore(0x00, caller())\n mstore(0x20, amount)\n log4(0x00, 0x40, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, shl(96, to)), id)\n }\n _afterTokenTransfer(msg.sender, to, id, amount);\n return true;\n }\n\n /// @dev Transfers `amount` of token `id` from `from` to `to`.\n ///\n /// Note: Does not update the allowance if it is the maximum uint256 value.\n ///\n /// Requirements:\n /// - `from` must at least have `amount` of token `id`.\n /// - The caller must have at least `amount` of allowance to transfer the\n /// tokens of `from` or approved as an operator.\n ///\n /// Emits a {Transfer} event.\n function transferFrom(address from, address to, uint256 id, uint256 amount)\n public\n payable\n virtual\n returns (bool)\n {\n _beforeTokenTransfer(from, to, id, amount);\n /// @solidity memory-safe-assembly\n assembly {\n // Compute the operator slot and load its value.\n mstore(0x34, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x28, from)\n mstore(0x14, caller())\n // Check if the caller is an operator.\n if iszero(sload(keccak256(0x20, 0x34))) {\n // Compute the allowance slot and load its value.\n mstore(0x00, id)\n let allowanceSlot := keccak256(0x00, 0x54)\n let allowance_ := sload(allowanceSlot)\n // If the allowance is not the maximum uint256 value.\n if add(allowance_, 1) {\n // Revert if the amount to be transferred exceeds the allowance.\n if gt(amount, allowance_) {\n mstore(0x00, 0xdeda9030) // `InsufficientPermission()`.\n revert(0x1c, 0x04)\n }\n // Subtract and store the updated allowance.\n sstore(allowanceSlot, sub(allowance_, amount))\n }\n }\n // Compute the balance slot and load its value.\n mstore(0x14, id)\n let fromBalanceSlot := keccak256(0x14, 0x40)\n let fromBalance := sload(fromBalanceSlot)\n // Revert if insufficient balance.\n if gt(amount, fromBalance) {\n mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.\n revert(0x1c, 0x04)\n }\n // Subtract and store the updated balance.\n sstore(fromBalanceSlot, sub(fromBalance, amount))\n // Compute the balance slot of `to`.\n mstore(0x28, to)\n mstore(0x14, id)\n let toBalanceSlot := keccak256(0x14, 0x40)\n let toBalanceBefore := sload(toBalanceSlot)\n let toBalanceAfter := add(toBalanceBefore, amount)\n // Revert if the balance overflows.\n if lt(toBalanceAfter, toBalanceBefore) {\n mstore(0x00, 0x89560ca1) // `BalanceOverflow()`.\n revert(0x1c, 0x04)\n }\n // Store the updated balance of `to`.\n sstore(toBalanceSlot, toBalanceAfter)\n // Emit the {Transfer} event.\n mstore(0x00, caller())\n mstore(0x20, amount)\n // forgefmt: disable-next-line\n log4(0x00, 0x40, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)), id)\n // Restore the part of the free memory pointer that has been overwritten.\n mstore(0x34, 0x00)\n }\n _afterTokenTransfer(from, to, id, amount);\n return true;\n }\n\n /// @dev Sets `amount` as the allowance of `spender` for the caller for token `id`.\n ///\n /// Emits a {Approval} event.\n function approve(address spender, uint256 id, uint256 amount)\n public\n payable\n virtual\n returns (bool)\n {\n /// @solidity memory-safe-assembly\n assembly {\n // Compute the allowance slot and store the amount.\n mstore(0x34, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x28, caller())\n mstore(0x14, spender)\n mstore(0x00, id)\n sstore(keccak256(0x00, 0x54), amount)\n // Emit the {Approval} event.\n mstore(0x00, amount)\n log4(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x20)), id)\n // Restore the part of the free memory pointer that has been overwritten.\n mstore(0x34, 0x00)\n }\n return true;\n }\n\n /// @dev Sets whether `operator` is approved to manage the tokens of the caller.\n ///\n /// Emits {OperatorSet} event.\n function setOperator(address operator, bool approved) public payable virtual returns (bool) {\n /// @solidity memory-safe-assembly\n assembly {\n // Convert `approved` to `0` or `1`.\n let approvedCleaned := iszero(iszero(approved))\n // Compute the operator slot and store the approved.\n mstore(0x20, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x14, caller())\n mstore(0x00, operator)\n sstore(keccak256(0x0c, 0x34), approvedCleaned)\n // Emit the {OperatorSet} event.\n mstore(0x20, approvedCleaned)\n log3(0x20, 0x20, _OPERATOR_SET_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))\n }\n return true;\n }\n\n /// @dev Returns true if this contract implements the interface defined by `interfaceId`.\n function supportsInterface(bytes4 interfaceId) public view virtual returns (bool result) {\n /// @solidity memory-safe-assembly\n assembly {\n let s := shr(224, interfaceId)\n // ERC165: 0x01ffc9a7, ERC6909: 0x0f632fb3.\n result := or(eq(s, 0x01ffc9a7), eq(s, 0x0f632fb3))\n }\n }\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* INTERNAL FUNCTIONS */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n /// @dev Mints `amount` of token `id` to `to`.\n ///\n /// Emits a {Transfer} event.\n function _mint(address to, uint256 id, uint256 amount) internal virtual {\n _beforeTokenTransfer(address(0), to, id, amount);\n /// @solidity memory-safe-assembly\n assembly {\n // Compute the balance slot.\n mstore(0x20, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x14, to)\n mstore(0x00, id)\n let toBalanceSlot := keccak256(0x00, 0x40)\n // Add and store the updated balance\n let toBalanceBefore := sload(toBalanceSlot)\n let toBalanceAfter := add(toBalanceBefore, amount)\n // Revert if the balance overflows.\n if lt(toBalanceAfter, toBalanceBefore) {\n mstore(0x00, 0x89560ca1) // `BalanceOverflow()`.\n revert(0x1c, 0x04)\n }\n sstore(toBalanceSlot, toBalanceAfter)\n // Emit the {Transfer} event.\n mstore(0x00, caller())\n mstore(0x20, amount)\n log4(0x00, 0x40, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, to)), id)\n }\n _afterTokenTransfer(address(0), to, id, amount);\n }\n\n /// @dev Burns `amount` token `id` from `from`.\n ///\n /// Emits a {Transfer} event.\n function _burn(address from, uint256 id, uint256 amount) internal virtual {\n _beforeTokenTransfer(from, address(0), id, amount);\n /// @solidity memory-safe-assembly\n assembly {\n // Compute the balance slot.\n mstore(0x20, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x14, from)\n mstore(0x00, id)\n let fromBalanceSlot := keccak256(0x00, 0x40)\n let fromBalance := sload(fromBalanceSlot)\n // Revert if insufficient balance.\n if gt(amount, fromBalance) {\n mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.\n revert(0x1c, 0x04)\n }\n // Subtract and store the updated balance.\n sstore(fromBalanceSlot, sub(fromBalance, amount))\n // Emit the {Transfer} event.\n mstore(0x00, caller())\n mstore(0x20, amount)\n log4(0x00, 0x40, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0, id)\n }\n _afterTokenTransfer(from, address(0), id, amount);\n }\n\n /// @dev Transfers `amount` of token `id` from `from` to `to`.\n ///\n /// Note: Does not update the allowance if it is the maximum uint256 value.\n ///\n /// Requirements:\n /// - `from` must at least have `amount` of token `id`.\n /// - If `by` is not the zero address,\n /// it must have at least `amount` of allowance to transfer the\n /// tokens of `from` or approved as an operator.\n ///\n /// Emits a {Transfer} event.\n function _transfer(address by, address from, address to, uint256 id, uint256 amount)\n internal\n virtual\n {\n _beforeTokenTransfer(from, to, id, amount);\n /// @solidity memory-safe-assembly\n assembly {\n let bitmaskAddress := 0xffffffffffffffffffffffffffffffffffffffff\n // Compute the operator slot and load its value.\n mstore(0x34, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x28, from)\n // If `by` is not the zero address.\n if and(bitmaskAddress, by) {\n mstore(0x14, by)\n // Check if the `by` is an operator.\n if iszero(sload(keccak256(0x20, 0x34))) {\n // Compute the allowance slot and load its value.\n mstore(0x00, id)\n let allowanceSlot := keccak256(0x00, 0x54)\n let allowance_ := sload(allowanceSlot)\n // If the allowance is not the maximum uint256 value.\n if add(allowance_, 1) {\n // Revert if the amount to be transferred exceeds the allowance.\n if gt(amount, allowance_) {\n mstore(0x00, 0xdeda9030) // `InsufficientPermission()`.\n revert(0x1c, 0x04)\n }\n // Subtract and store the updated allowance.\n sstore(allowanceSlot, sub(allowance_, amount))\n }\n }\n }\n // Compute the balance slot and load its value.\n mstore(0x14, id)\n let fromBalanceSlot := keccak256(0x14, 0x40)\n let fromBalance := sload(fromBalanceSlot)\n // Revert if insufficient balance.\n if gt(amount, fromBalance) {\n mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.\n revert(0x1c, 0x04)\n }\n // Subtract and store the updated balance.\n sstore(fromBalanceSlot, sub(fromBalance, amount))\n // Compute the balance slot of `to`.\n mstore(0x28, to)\n mstore(0x14, id)\n let toBalanceSlot := keccak256(0x14, 0x40)\n let toBalanceBefore := sload(toBalanceSlot)\n let toBalanceAfter := add(toBalanceBefore, amount)\n // Revert if the balance overflows.\n if lt(toBalanceAfter, toBalanceBefore) {\n mstore(0x00, 0x89560ca1) // `BalanceOverflow()`.\n revert(0x1c, 0x04)\n }\n // Store the updated balance of `to`.\n sstore(toBalanceSlot, toBalanceAfter)\n // Emit the {Transfer} event.\n mstore(0x00, and(bitmaskAddress, by))\n mstore(0x20, amount)\n // forgefmt: disable-next-line\n log4(0x00, 0x40, _TRANSFER_EVENT_SIGNATURE, and(bitmaskAddress, from), and(bitmaskAddress, to), id)\n // Restore the part of the free memory pointer that has been overwritten.\n mstore(0x34, 0x00)\n }\n _afterTokenTransfer(from, to, id, amount);\n }\n\n /// @dev Sets `amount` as the allowance of `spender` for `owner` for token `id`.\n ///\n /// Emits a {Approval} event.\n function _approve(address owner, address spender, uint256 id, uint256 amount)\n internal\n virtual\n {\n /// @solidity memory-safe-assembly\n assembly {\n // Compute the allowance slot and store the amount.\n mstore(0x34, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x28, owner)\n mstore(0x14, spender)\n mstore(0x00, id)\n sstore(keccak256(0x00, 0x54), amount)\n // Emit the {Approval} event.\n mstore(0x00, amount)\n // forgefmt: disable-next-line\n log4(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, mload(0x34)), shr(96, mload(0x20)), id)\n // Restore the part of the free memory pointer that has been overwritten.\n mstore(0x34, 0x00)\n }\n }\n\n /// @dev Sets whether `operator` is approved to manage the tokens of `owner`.\n ///\n /// Emits {OperatorSet} event.\n function _setOperator(address owner, address operator, bool approved) internal virtual {\n /// @solidity memory-safe-assembly\n assembly {\n // Convert `approved` to `0` or `1`.\n let approvedCleaned := iszero(iszero(approved))\n // Compute the operator slot and store the approved.\n mstore(0x20, _ERC6909_MASTER_SLOT_SEED)\n mstore(0x14, owner)\n mstore(0x00, operator)\n sstore(keccak256(0x0c, 0x34), approvedCleaned)\n // Emit the {OperatorSet} event.\n mstore(0x20, approvedCleaned)\n // forgefmt: disable-next-line\n log3(0x20, 0x20, _OPERATOR_SET_EVENT_SIGNATURE, shr(96, shl(96, owner)), shr(96, mload(0x0c)))\n }\n }\n\n /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/\n /* HOOKS TO OVERRIDE */\n /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/\n\n /// @dev Hook that is called before any transfer of tokens.\n /// This includes minting and burning.\n function _beforeTokenTransfer(address from, address to, uint256 id, uint256 amount)\n internal\n virtual\n {}\n\n /// @dev Hook that is called after any transfer of tokens.\n /// This includes minting and burning.\n function _afterTokenTransfer(address from, address to, uint256 id, uint256 amount)\n internal\n virtual\n {}\n}\n"}},"settings":{"remappings":["@forge/=lib/accounts/lib/forge-std/src/","@solady/=lib/accounts/lib/solady/","accounts/=lib/accounts/src/","forge-std/=lib/forge-std/src/","solady/=lib/solady/src/"],"optimizer":{"enabled":true,"runs":9999999},"metadata":{"useLiteralContent":false,"bytecodeHash":"ipfs","appendCBOR":true},"outputSelection":{"*":{"*":["abi","evm.bytecode","evm.deployedBytecode","evm.methodIdentifiers","metadata"]}},"evmVersion":"cancun","viaIR":false,"libraries":{}}} diff --git a/lib/accounts b/lib/accounts index 4789484..afe4353 160000 --- a/lib/accounts +++ b/lib/accounts @@ -1 +1 @@ -Subproject commit 4789484b1daa1e7826eeec6833ca9b47824ee8b6 +Subproject commit afe43537c858341bcb93d2981f4d0260290a60dc diff --git a/lib/solady b/lib/solady index f20771a..8c07ea8 160000 --- a/lib/solady +++ b/lib/solady @@ -1 +1 @@ -Subproject commit f20771a9688990cfd3065a288baccced010f4b56 +Subproject commit 8c07ea8008069eb9f463b1992eb3a70d11d22d2a diff --git a/src/Ragequitter.sol b/src/Ragequitter.sol index f11f765..34c9209 100644 --- a/src/Ragequitter.sol +++ b/src/Ragequitter.sol @@ -162,11 +162,13 @@ contract Ragequitter is ERC6909 { /// @dev Mints loot shares in exchange for tribute `amount` to an `account`. /// If no `tribute` is set, then function will revert on `safeTransferFrom`. - function contribute(address account, uint256 amount) public payable virtual { + function contribute(address account, uint96 amount) public payable virtual { address tribute = _settings[account].tribute; if (tribute == ETH) _safeTransferETH(account, amount); else _safeTransferFrom(tribute, msg.sender, account, amount); - _mint(msg.sender, uint256(uint160(account)), amount); + uint256 id = uint256(uint160(account)); + _metadata[id].totalSupply += amount; + _mint(msg.sender, id, amount); } /// ======================== INSTALLATION ======================== ///