Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
z0r0z committed Apr 29, 2024
1 parent 636bba9 commit ed98f58
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 55 deletions.
8 changes: 4 additions & 4 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
RagequitterTest:testDeploy() (gas: 1554687)
RagequitterTest:testInstall() (gas: 30190)
RagequitterTest:testDeploy() (gas: 1733335)
RagequitterTest:testInstall() (gas: 34130)
RagequitterTest:testMint() (gas: 79537)
RagequitterTest:testRagequit() (gas: 166322)
RagequitterTest:testSetURI() (gas: 36008)
RagequitterTest:testRagequit() (gas: 170251)
RagequitterTest:testSetURI() (gas: 35986)
146 changes: 95 additions & 51 deletions src/Ragequitter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,42 @@ pragma solidity ^0.8.19;

import {ERC6909} from "@solady/src/tokens/ERC6909.sol";

/// @notice Simple ragequitter singleton. Uses ERC6909 minimal multitoken.
/// @notice Simple ragequit singleton with ERC6909 accounting. Version 1.
contract Ragequitter is ERC6909 {
/// ======================= CUSTOM ERRORS ======================= ///

/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();

/// @dev Invalid time window for ragequit.
error InvalidTime();

// @dev Out-of-order redemption assets.
/// @dev Out-of-order redemption assets.
error InvalidAssetOrder();

/// @dev Overflow or division by zero.
error MulDivFailed();

/// @dev ERC20 `transferFrom` failed.
error TransferFromFailed();

/// @dev ETH transfer failed.
error ETHTransferFailed();

/// =========================== EVENTS =========================== ///

/// @dev Logs new loot metadata setting.
/// @dev Logs new account loot metadata.
event URI(string metadata, uint256 indexed id);

/// @dev Logs new authority contract for an account.
event AuthSet(address indexed account, IAuth auth);
/// @dev Logs new account authority contract.
event AuthSet(address indexed account, IAuth authority);

/// @dev Logs new account contribution asset setting.
event TributeSet(address indexed account, address tribute);

/// @dev Logs new account ragequit time validity setting.
event TimeValiditySet(address indexed account, uint48 validAfter, uint48 validUntil);

/// ========================== STRUCTS ========================== ///

/// @dev The account loot metadata struct.
/// @dev The account loot shares metadata struct.
struct Metadata {
string name;
string symbol;
Expand All @@ -42,18 +48,24 @@ contract Ragequitter is ERC6909 {
uint96 totalSupply;
}

/// @dev The account loot shares struct.
/// @dev The account loot shares ownership struct.
struct Ownership {
address owner;
uint96 shares;
}

/// @dev The account ragequit settings struct.
/// @dev The account loot shares settings struct.
struct Settings {
address tribute;
uint48 validAfter;
uint48 validUntil;
}

/// ========================= CONSTANTS ========================= ///

/// @dev The conventional ERC7528 ETH address.
address internal constant ETH = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

/// ========================== STORAGE ========================== ///

/// @dev Stores mapping of metadata settings to account token IDs.
Expand Down Expand Up @@ -126,6 +138,35 @@ contract Ragequitter is ERC6909 {
}
}

/// ============================ LOOT ============================ ///

/// @dev Mints loot shares for an owner of the caller account.
function mint(address owner, uint96 shares) public payable virtual {
uint256 id = uint256(uint160(msg.sender));
_metadata[id].totalSupply += shares;
_mint(owner, id, shares);
}

/// @dev Burns loot shares from an owner of the caller account.
function burn(address owner, uint96 shares) public payable virtual {
uint256 id = uint256(uint160(msg.sender));
unchecked {
_metadata[id].totalSupply -= shares;
}
_burn(owner, id, shares);
}

/// ========================== TRIBUTE ========================== ///

/// @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 {
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);
}

/// ======================== INSTALLATION ======================== ///

/// @dev Initializes ragequit settings for the caller account.
Expand All @@ -134,44 +175,30 @@ contract Ragequitter is ERC6909 {
virtual
{
uint256 id = uint256(uint160(msg.sender));
_settings[msg.sender] = Settings(setting.validAfter, setting.validUntil);
if (owners.length != 0) {
uint96 supply;
for (uint256 i; i != owners.length;) {
for (uint256 i; i != owners.length; ++i) {
supply += owners[i].shares;
_mint(owners[i].owner, id, owners[i].shares);
unchecked {
++i;
}
}
_metadata[id].totalSupply += supply;
}
if (bytes(meta.name).length != 0) {
_metadata[id].name = meta.name;
_metadata[id].symbol = meta.symbol;
}
if (bytes(meta.tokenURI).length != 0) _metadata[id].tokenURI = meta.tokenURI;
if (meta.authority != IAuth(address(0))) _metadata[id].authority = meta.authority;
}

/// @dev Sets new authority contract for the caller account.
function setAuth(IAuth auth) public virtual {
emit AuthSet(msg.sender, (_metadata[uint256(uint160(msg.sender))].authority = auth));
}

/// @dev Sets account and loot token URI `metadata`.
function setURI(string calldata metadata) public virtual {
uint256 id = uint256(uint160(msg.sender));
emit URI(_metadata[id].tokenURI = metadata, id);
}

/// @dev Sets account ragequit time validity (or 'timespan').
function setTimeValidity(uint48 validAfter, uint48 validUntil) public virtual {
_settings[msg.sender] = Settings(validAfter, validUntil);
emit TimeValiditySet(msg.sender, validAfter, validUntil);
if (bytes(meta.tokenURI).length != 0) {
emit URI((_metadata[id].tokenURI = meta.tokenURI), id);
}
if (meta.authority != IAuth(address(0))) {
emit AuthSet(msg.sender, (_metadata[id].authority = meta.authority));
}
_settings[msg.sender] = Settings(setting.tribute, setting.validAfter, setting.validUntil);
emit TimeValiditySet(msg.sender, setting.validAfter, setting.validUntil);
emit TributeSet(msg.sender, setting.tribute);
}

/// ============================ LOOT ============================ ///
/// ==================== SETTINGS & METADATA ==================== ///

/// @dev Returns the account metadata.
function getMetadata(address account)
Expand All @@ -184,29 +211,36 @@ contract Ragequitter is ERC6909 {
return (meta.name, meta.symbol, meta.tokenURI, meta.authority);
}

/// @dev Returns the account ragequit time validity settings.
function getSettings(address account) public view virtual returns (uint48, uint48) {
/// @dev Returns the account tribute and ragequit time validity settings.
function getSettings(address account) public view virtual returns (address, uint48, uint48) {
Settings storage setting = _settings[account];
return (setting.validAfter, setting.validUntil);
return (setting.tribute, setting.validAfter, setting.validUntil);
}

/// @dev Mints loot shares for an owner of the caller account.
function mint(address owner, uint96 shares) public payable virtual {
uint256 id = uint256(uint160(msg.sender));
_metadata[id].totalSupply += shares;
_mint(owner, id, shares);
/// @dev Sets new authority contract for the caller account.
function setAuth(IAuth auth) public virtual {
emit AuthSet(msg.sender, (_metadata[uint256(uint160(msg.sender))].authority = auth));
}

/// @dev Burns loot shares from an owner of the caller account.
function burn(address owner, uint96 shares) public payable virtual {
/// @dev Sets account and loot token URI `metadata`.
function setURI(string calldata metadata) public virtual {
uint256 id = uint256(uint160(msg.sender));
unchecked {
_metadata[id].totalSupply -= shares;
}
_burn(owner, id, shares);
emit URI((_metadata[id].tokenURI = metadata), id);
}

/// @dev Sets account ragequit time validity (or 'timespan').
function setTimeValidity(uint48 validAfter, uint48 validUntil) public virtual {
emit TimeValiditySet(
msg.sender, _settings[msg.sender].validAfter, _settings[msg.sender].validUntil
);
}

/// @dev Sets account contribution asset (tribute).
function setTribute(address tribute) public virtual {
emit TributeSet(msg.sender, _settings[msg.sender].tribute = tribute);
}

/// =================== EXTERNAL TOKEN HELPERS =================== ///
/// =================== EXTERNAL ASSET HELPERS =================== ///

/// @dev Returns the `amount` of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
Expand All @@ -230,6 +264,16 @@ contract Ragequitter is ERC6909 {
}
}

/// @dev Sends `amount` (in wei) ETH to `to`.
function _safeTransferETH(address to, uint256 amount) internal virtual {
assembly ("memory-safe") {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}

/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
function _safeTransferFrom(address token, address from, address to, uint256 amount)
internal
Expand Down

0 comments on commit ed98f58

Please sign in to comment.