Skip to content

Commit

Permalink
Merge pull request #4 from koloz193/flavors
Browse files Browse the repository at this point in the history
moved deadman trigger to be an extension
  • Loading branch information
koloz193 authored Nov 29, 2022
2 parents ee9f8d8 + 95865c1 commit 4d19efa
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 102 deletions.
20 changes: 15 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ A general mixin for any owned/permissioned nft contract that allows for fine tun
```ml
royalty-guard
├─ RoyaltyGuard — "Admin controlled ALLOW/DENY list primitives"
├─ extensions
| ├─ RoyaltyGuardDeadmanTrigger - "RoyaltyGuard with a renewable deadman switch to turn list off after predefined period of time"
examples
├─ deadman-trigger
| ├─ DeadmanGuardedERC721 - "GuardedERC721 with the deadman trigger extension"
| ├─ DeadmanGuardedERC1155 - "GuardedERC1155 with the deadman trigger extension"
├─ GuardedERC721 — "Solmate based ERC721 with Owner restrictions and RoyaltyGuard"
├─ GuardedERC1155 — "Solmate based ERC1155 with Owner restrictionsand RoyaltyGuard"
```
Expand All @@ -23,12 +28,18 @@ We **do not give any warranties** and **will not be liable for any loss** incurr
`RoyaltyGuard` is an abstract contract that is meant to be inherrited to integrate with a contract. The main features of the contract are:
1. Configurable list type with options `OFF`, `ALLOW`, and `DENY`.
2. Flexible admin permissioning of Guard (note examples use `Owner` based permissioning but can leave it to anyone, `ROLE` based, etc.)
3. Deadman Trigger Support
- If the owner/permissioning group hasn't renewed the trigger, anyone can come in and activate the deadman trigger turning the list type to `OFF`
- Even after deadman trigger has been activated, the owner/permissioning group can renew and change the list type to `ALLOW` or `DENY`.

The two different active list types are `ALLOW` and `DENY`. In the `ALLOW` model, functions marked with the `checkList` modifier will `revert` with `Unauthorized` unless the supplied address is on the list. The `DENY` list takes the opposite approach and will `revert` if and only if the supplied address is on the list.

### Extensions

#### Deadman Trigger
If the owner/permissioning group hasn't renewed the trigger, anyone can come in and activate the deadman trigger turning the list type to `OFF`. Even after deadman trigger has been activated, the owner/permissioning group can renew and change the list type to `ALLOW` or `DENY`.

The two deadman trigger functions are `setDeadmanListTriggerRenewalDuration(uint256 _numYears)`, another admin guarded function, that renews the deadman switch for `_numYears` years and `activateDeadmanListTrigger()`, a public function, used to turn the list type to `OFF` and is only callable when the current `block.timestamp` is on or after the returned value from `getDeadmanTriggerAvailableDatetime()`.

Source code at [RoyaltyGuardDeadmanTrigger](src/royalty-guard/extensions/RoyaltyGuardDeadmanTrigger.sol) with [examples](src/example/deadman-trigger/).

## How to Integrate

While there are other ways to integrate with `RoyaltyGuard`, these examples use the least amount of up front code relying on post contract deployment interactions to finish setup.
Expand All @@ -42,14 +53,13 @@ Example setups can be found in [GuardedERC721](src/example/GuardedERC721.sol) an
For more advance setups, a set of internal functions is supplied that can be used for the purpose of setup within a contracts constructor. See [RoyaltyGuard](src/royalty-guard/RoyaltyGuard.sol).

## Usage

Guarded functions are those that are marked with the modifier `checkList(address _addr)` that checks the list type and changes based on the typing.

The list type can be updated via `toggleListType(ListType _newListType)` which relies on the implemented function `hasAdminPermission(address _addr) returns (bool)`. The examples leverage use of an `Owner` to guard access. Valid inputs for `ListType` are `0` for OFF, `1` for ALLOW, and `2` for DENY.

Adding, removing, and clearing a list also rely on `hasAdminPermission(address _addr) returns (bool)`. The relevant functions here are `batchAddAddressToRoyaltyList(ListType _listType, address[] _addrs)`, `batchRemoveAddressToRoyaltyList(ListType _listType, address[] _addrs)`, and `clearList(ListType _listType)`.

The two deadman trigger functions are `setDeadmanListTriggerRenewalDuration(uint256 _numYears)`, another admin guarded function, that renews the deadman switch for `_numYears` years and `activateDeadmanListTrigger()`, a public function, used to turn the list type to `OFF` and is only callable when the current `block.timestamp` is on or after the returned value from `getDeadmanTriggerAvailableDatetime()`.

## Installation

To install with [**Foundry**](https://github.com/gakonst/foundry):
Expand Down
82 changes: 82 additions & 0 deletions src/example/deadman-trigger/DeadmanGuardedERC1155.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {ERC1155} from "solmate/tokens/ERC1155.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {RoyaltyGuardDeadmanTrigger, RoyaltyGuard, IRoyaltyGuard} from "../../royalty-guard/extensions/RoyaltyGuardDeadmanTrigger.sol";

contract DeadmanGuardedERC1155 is ERC1155, Owned, RoyaltyGuardDeadmanTrigger {

/*//////////////////////////////////////////////////////////////////////////
PRIVATE STORAGE
//////////////////////////////////////////////////////////////////////////*/
string private baseURI;
string private name_;
string private symbol_;
uint256 private tokenIdCounter;

/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////*/
constructor(string memory _name, string memory _symbol, string memory _baseURI, address _newOwner)
Owned(_newOwner)
{
baseURI = _baseURI;
name_ = _name;
symbol_ = _symbol;
}

/*//////////////////////////////////////////////////////////////////////////
ERC1155 LOGIC
//////////////////////////////////////////////////////////////////////////*/
function uri(uint256 _id) public view virtual override returns (string memory) {
return string(abi.encodePacked(baseURI, _id));
}

/// @notice Returns the name of this 1155 contract.
/// @return name of contract
function name() external view returns (string memory) {
return name_;
}

/// @notice Returns the symbol of this 1155 contract.
/// @return symbol of contract
function symbol() external view returns (string memory) {
return symbol_;
}

/// @notice Create a new token sending the full {_amount} to {_to}.
/// @dev Must be contract owner to mint new token.
function mint(address _to, uint256 _amount) external onlyOwner{
_mint(_to, tokenIdCounter++, _amount, "");
}

/// @notice Destroy {_amount} of token with id {_id}.
/// @dev Must have a balance >= {_amount} of {_tokenId}.
function burn(address _from, uint256 _id, uint256 _amount) external {
if (balanceOf[_from][_id] < _amount) revert("INSUFFICIENT BALANCE");
_burn(_from, _id, _amount);
}

/*//////////////////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////////////////*/

function supportsInterface(bytes4 _interfaceId) public view virtual override (ERC1155, RoyaltyGuard) returns (bool) {
return RoyaltyGuard.supportsInterface(_interfaceId) || ERC1155.supportsInterface(_interfaceId);
}

/*//////////////////////////////////////////////////////////////////////////
RoyaltyGuard LOGIC
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc RoyaltyGuard
function hasAdminPermission(address _addr) public view virtual override (IRoyaltyGuard, RoyaltyGuard) returns (bool) {
return _addr == owner;
}

/// @dev Guards {setApprovalForAll} based on the type of list and depending if {_operator} is on the list.
function setApprovalForAll(address _operator, bool _approved) public virtual override checkList(_operator) {
super.setApprovalForAll(_operator, _approved);
}
}
76 changes: 76 additions & 0 deletions src/example/deadman-trigger/DeadmanGuardedERC721.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {ERC721} from "solmate/tokens/ERC721.sol";
import {Owned} from "solmate/auth/Owned.sol";
import {RoyaltyGuardDeadmanTrigger, RoyaltyGuard, IRoyaltyGuard} from "../../royalty-guard/extensions/RoyaltyGuardDeadmanTrigger.sol";

contract DeadmanGuardedERC721 is ERC721, Owned, RoyaltyGuardDeadmanTrigger {

/*//////////////////////////////////////////////////////////////////////////
PRIVATE STORAGE
//////////////////////////////////////////////////////////////////////////*/
string private baseURI;
uint256 private tokenCounter;

/*//////////////////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////////////////*/
constructor(string memory _name, string memory _symbol, string memory _baseURI, address _newOwner)
ERC721(_name, _symbol)
Owned(_newOwner)
{
baseURI = _baseURI;
}

/*//////////////////////////////////////////////////////////////////////////
ERC721 LOGIC
//////////////////////////////////////////////////////////////////////////*/

/// @notice Retrieve the tokenURI for a token with the supplied _id
/// @dev Wont throw or revert given a nonexistent tokenId
/// @return string token uri
function tokenURI(uint256 _id) public view virtual override returns (string memory) {
return string(abi.encodePacked(baseURI, _id));
}

/// @notice Create a new token sending directly to {_to}.
/// @dev Must be contract owner to mint new token.
function mint(address _to) external onlyOwner {
_mint(_to, tokenCounter++);
}

/// @notice Destroy token with id {_id}.
/// @dev Must be the token owner to call.
function burn(uint256 _id) external {
if (_ownerOf[_id] != msg.sender) revert("NOT_OWNER");
_burn(_id);
}

/*//////////////////////////////////////////////////////////////////////////
ERC165 LOGIC
//////////////////////////////////////////////////////////////////////////*/

function supportsInterface(bytes4 _interfaceId) public view virtual override (ERC721, RoyaltyGuard) returns (bool) {
return RoyaltyGuard.supportsInterface(_interfaceId) || ERC721.supportsInterface(_interfaceId);
}

/*//////////////////////////////////////////////////////////////////////////
RoyaltyGuard LOGIC
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc RoyaltyGuard
function hasAdminPermission(address _addr) public view virtual override (IRoyaltyGuard, RoyaltyGuard) returns (bool) {
return _addr == owner;
}

/// @dev Guards {approve} based on the type of list and depending if {_spender} is on the list.
function approve(address _spender, uint256 _id) public virtual override checkList(_spender) {
super.approve(_spender, _id);
}

/// @dev Guards {setApprovalForAll} based on the type of list and depending if {_operator} is on the list.
function setApprovalForAll(address _operator, bool _approved) public virtual override checkList(_operator) {
super.setApprovalForAll(_operator, _approved);
}
}
20 changes: 0 additions & 20 deletions src/royalty-guard/IRoyaltyGuard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,9 @@ interface IRoyaltyGuard {
/// @notice Emitted when an address is removed from a list.
event AddressRemovedList(address indexed _updater, address indexed _removedAddr, ListType indexed _ListType);

/// @notice Emitted when deadman trigger datetime has been updated.
event DeadmanTriggerDatetimeUpdated(address indexed _updater, uint256 _oldDatetime, uint256 _newDatetime);

/// @notice Emitted when a list is cleared.
event ListCleared(address indexed _updater, ListType _listType);

/// @notice Emitted when the deadman switch is activated.
event DeadmanTriggerActivated(address indexed _activator);

/*//////////////////////////////////////////////////////////////////////////
Custom Errors
//////////////////////////////////////////////////////////////////////////*/
Expand All @@ -49,9 +43,6 @@ interface IRoyaltyGuard {
/// @notice Emitted when trying to add an address to a list with type OFF.
error CantAddToOFFList();

/// @notice Emitted when the deadman trigger datetime threshold hasnt passed but tries to get called.
error DeadmanTriggerStillActive();

/// @notice Emitted when an admin only function tries to be called by a non-admin.
error MustBeAdmin();

Expand All @@ -73,17 +64,10 @@ interface IRoyaltyGuard {
/// @param _addrs being removed from the designated list
function batchRemoveAddressToRoyaltyList(IRoyaltyGuard.ListType _listType, address[] calldata _addrs) external;

/// @notice Sets the deadman list trigger for the specified number of years from current block timestamp
/// @param _numYears to renew the trigger for.
function setDeadmanListTriggerRenewalDuration(uint256 _numYears) external;

/// @notice Clears an entire list.
/// @param _listType of list being cleared.
function clearList(IRoyaltyGuard.ListType _listType) external;

/// @notice Triggers the deadman switch for the list
function activateDeadmanListTrigger() external;

/*//////////////////////////////////////////////////////////////////////////
External Read Functions
//////////////////////////////////////////////////////////////////////////*/
Expand All @@ -110,8 +94,4 @@ interface IRoyaltyGuard {
/// @notice Returns the ListType currently being used;
/// @return ListType of the list. Values are: 0 (OFF), 1 (ALLOW), 2 (DENY)
function getListType() external view returns (ListType);

/// @notice The datetime threshold after which the deadman trigger can be called by anyone.
/// @return uint256 denoting unix epoch time after which the deadman trigger can be activated.
function getDeadmanTriggerAvailableDatetime() external view returns (uint256);
}
51 changes: 10 additions & 41 deletions src/royalty-guard/RoyaltyGuard.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ abstract contract RoyaltyGuard is IRoyaltyGuard, ERC165 {

mapping(IRoyaltyGuard.ListType => EnumerableSet.AddressSet) private list;
IRoyaltyGuard.ListType private listType;
uint256 public deadmanListTriggerAfterDatetime;

/*//////////////////////////////////////////////////////////////////////////
Modifiers
Expand Down Expand Up @@ -50,88 +49,65 @@ abstract contract RoyaltyGuard is IRoyaltyGuard, ERC165 {

/// @dev Only the contract owner can call this function.
/// @inheritdoc IRoyaltyGuard
function toggleListType(IRoyaltyGuard.ListType _newListType) external onlyAdmin {
function toggleListType(IRoyaltyGuard.ListType _newListType) external virtual onlyAdmin {
_setListType(_newListType);
}

/// @dev Only the contract owner can call this function.
/// @dev Cannot add to the OFF list
/// @inheritdoc IRoyaltyGuard
function batchAddAddressToRoyaltyList(IRoyaltyGuard.ListType _listType, address[] calldata _addrs) external onlyAdmin {
function batchAddAddressToRoyaltyList(IRoyaltyGuard.ListType _listType, address[] calldata _addrs) external virtual onlyAdmin {
if (_listType == IRoyaltyGuard.ListType.OFF) revert IRoyaltyGuard.CantAddToOFFList();
_batchUpdateList(_listType, _addrs, true);
}

/// @dev Only the contract owner can call this function.
/// @inheritdoc IRoyaltyGuard
function batchRemoveAddressToRoyaltyList(IRoyaltyGuard.ListType _listType, address[] calldata _addrs) external onlyAdmin {
function batchRemoveAddressToRoyaltyList(IRoyaltyGuard.ListType _listType, address[] calldata _addrs) external virtual onlyAdmin {
_batchUpdateList(_listType, _addrs, false);
}

/// @dev Only the contract owner can call this function.
/// @inheritdoc IRoyaltyGuard
function setDeadmanListTriggerRenewalDuration(uint256 _numYears) external onlyAdmin {
_setDeadmanTriggerRenewalInYears(_numYears);
}

/// @dev Only the contract owner can call this function.
/// @inheritdoc IRoyaltyGuard
function clearList(IRoyaltyGuard.ListType _listType) external onlyAdmin {
function clearList(IRoyaltyGuard.ListType _listType) external virtual onlyAdmin {
delete list[_listType];
emit ListCleared(msg.sender, _listType);
}

/*//////////////////////////////////////////////////////////////////////////
Public Write Functions
//////////////////////////////////////////////////////////////////////////*/

/// @dev Can only be called if deadmanListTriggerAfterDatetime is in the past.
/// @inheritdoc IRoyaltyGuard
function activateDeadmanListTrigger() external {
if (deadmanListTriggerAfterDatetime > block.timestamp) revert IRoyaltyGuard.DeadmanTriggerStillActive();
listType = IRoyaltyGuard.ListType.OFF;
emit DeadmanTriggerActivated(msg.sender);
}

/*//////////////////////////////////////////////////////////////////////////
Public Read Functions
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc IRoyaltyGuard
function getList(IRoyaltyGuard.ListType _listType) external view returns (address[] memory) {
function getList(IRoyaltyGuard.ListType _listType) external virtual view returns (address[] memory) {
return list[_listType].values();
}

/// @inheritdoc IRoyaltyGuard
function getInUseList() external view returns (address[] memory) {
function getInUseList() external virtual view returns (address[] memory) {
return list[listType].values();
}

/// @inheritdoc IRoyaltyGuard
function isOperatorInList(address _operator) external view returns (bool) {
function isOperatorInList(address _operator) external virtual view returns (bool) {
return list[listType].contains(_operator);
}

/// @inheritdoc IRoyaltyGuard
function getListType() external view returns (IRoyaltyGuard.ListType) {
function getListType() external virtual view returns (IRoyaltyGuard.ListType) {
return listType;
}

/// @inheritdoc IRoyaltyGuard
function getDeadmanTriggerAvailableDatetime() external view returns (uint256) {
return deadmanListTriggerAfterDatetime;
}

/// @dev used in the {onlyAdmin} modifier
/// @inheritdoc IRoyaltyGuard
function hasAdminPermission(address _addr) public view virtual returns (bool);
function hasAdminPermission(address _addr) public virtual view returns (bool);

/*//////////////////////////////////////////////////////////////////////////
ERC165 Overrides
//////////////////////////////////////////////////////////////////////////*/

/// @inheritdoc ERC165
function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) {
function supportsInterface(bytes4 _interfaceId) public virtual view override returns (bool) {
return _interfaceId == type(IRoyaltyGuard).interfaceId || super.supportsInterface(_interfaceId);
}

Expand Down Expand Up @@ -159,11 +135,4 @@ abstract contract RoyaltyGuard is IRoyaltyGuard, ERC165 {
}
}
}

/// @dev Internal method to set deadman trigger datetime. Main usage is constructor.
function _setDeadmanTriggerRenewalInYears(uint256 _numYears) internal {
uint256 newDatetime = block.timestamp + _numYears * 365 days;
emit DeadmanTriggerDatetimeUpdated(msg.sender, deadmanListTriggerAfterDatetime, newDatetime);
deadmanListTriggerAfterDatetime = newDatetime;
}
}
Loading

0 comments on commit 4d19efa

Please sign in to comment.