Skip to content

Commit 339cac7

Browse files
authored
Evolving NFTs (#443)
* Create RulesEngine * Create SharedMetadataBatch * add _getURIFromSharedMetadata * Create Evolving NFT * get an id instead of creating one, in createSharedMetadata * Add deleteSharedMetadata * rename create -> set * Assign shared metadata based on score ranges * Use ERC721AQueryable -> turn into dynamic contract * Add tests for EvolvingNFT * add tests for rules engine * Add getAllRules to RulesEngine * Standardize shared metadata storage location * Move contract to /unaudited
1 parent e1d4c3d commit 339cac7

File tree

9 files changed

+1823
-0
lines changed

9 files changed

+1823
-0
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
import "../../extension/interface/IRulesEngine.sol";
5+
6+
import "../../eip/interface/IERC20.sol";
7+
import "../../eip/interface/IERC721.sol";
8+
import "../../eip/interface/IERC1155.sol";
9+
10+
library RulesEngineStorage {
11+
bytes32 public constant RULES_ENGINE_STORAGE_POSITION = keccak256("rules.engine.storage");
12+
13+
struct Data {
14+
uint256 nextRuleId;
15+
mapping(uint256 => IRulesEngine.RuleWithId) rules;
16+
}
17+
18+
function rulesEngineStorage() internal pure returns (Data storage rulesEngineData) {
19+
bytes32 position = RULES_ENGINE_STORAGE_POSITION;
20+
assembly {
21+
rulesEngineData.slot := position
22+
}
23+
}
24+
}
25+
26+
abstract contract RulesEngine is IRulesEngine {
27+
/*///////////////////////////////////////////////////////////////
28+
View functions
29+
//////////////////////////////////////////////////////////////*/
30+
31+
function getScore(address _tokenOwner) public view returns (uint256 score) {
32+
uint256 len = _rulesEngineStorage().nextRuleId;
33+
34+
for (uint256 i = 0; i < len; i += 1) {
35+
Rule memory rule = _rulesEngineStorage().rules[i].rule;
36+
37+
if (rule.tokenType == TokenType.ERC20) {
38+
if (IERC20(rule.token).balanceOf(_tokenOwner) >= rule.balance) {
39+
score += rule.score;
40+
}
41+
} else if (rule.tokenType == TokenType.ERC721) {
42+
if (IERC721(rule.token).balanceOf(_tokenOwner) >= rule.balance) {
43+
score += rule.score;
44+
}
45+
} else if (rule.tokenType == TokenType.ERC1155) {
46+
if (IERC1155(rule.token).balanceOf(_tokenOwner, rule.tokenId) >= rule.balance) {
47+
score += rule.score;
48+
}
49+
}
50+
}
51+
}
52+
53+
function getAllRules() external view returns (RuleWithId[] memory rules) {
54+
uint256 len = _rulesEngineStorage().nextRuleId;
55+
uint256 count = 0;
56+
57+
for (uint256 i = 0; i < len; i += 1) {
58+
if (_rulesEngineStorage().rules[i].rule.token != address(0)) {
59+
count++;
60+
}
61+
}
62+
63+
rules = new RuleWithId[](count);
64+
uint256 idx = 0;
65+
for (uint256 j = 0; j < len; j += 1) {
66+
if (_rulesEngineStorage().rules[j].rule.token != address(0)) {
67+
rules[idx++] = _rulesEngineStorage().rules[j];
68+
}
69+
}
70+
}
71+
72+
/*///////////////////////////////////////////////////////////////
73+
External functions
74+
//////////////////////////////////////////////////////////////*/
75+
76+
function createRule(Rule memory rule) external returns (uint256 ruleId) {
77+
require(_canSetRules(), "RulesEngine: cannot set rules");
78+
ruleId = _createRule(rule);
79+
}
80+
81+
function deleteRule(uint256 _ruleId) external {
82+
require(_canSetRules(), "RulesEngine: cannot set rules");
83+
_deleteRule(_ruleId);
84+
}
85+
86+
/*///////////////////////////////////////////////////////////////
87+
Internal functions
88+
//////////////////////////////////////////////////////////////*/
89+
90+
function _createRule(Rule memory _rule) internal returns (uint256 ruleId) {
91+
ruleId = _rulesEngineStorage().nextRuleId++;
92+
_rulesEngineStorage().rules[ruleId] = RuleWithId(ruleId, _rule);
93+
94+
emit RuleCreated(ruleId, _rule);
95+
}
96+
97+
function _deleteRule(uint256 _ruleId) internal {
98+
delete _rulesEngineStorage().rules[_ruleId];
99+
}
100+
101+
function _rulesEngineStorage() internal pure returns (RulesEngineStorage.Data storage data) {
102+
data = RulesEngineStorage.rulesEngineStorage();
103+
}
104+
105+
function _canSetRules() internal view virtual returns (bool);
106+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
import "../../lib/NFTMetadataRendererLib.sol";
5+
import "../../extension/interface/ISharedMetadataBatch.sol";
6+
import "../../openzeppelin-presets/utils/EnumerableSet.sol";
7+
8+
/**
9+
* @title Shared Metadata Batch
10+
* @notice Store a batch of shared metadata for NFTs
11+
*/
12+
library SharedMetadataBatchStorage {
13+
bytes32 public constant SHARED_METADATA_BATCH_STORAGE_POSITION = keccak256("shared.metadata.batch.storage");
14+
15+
struct Data {
16+
EnumerableSet.Bytes32Set ids;
17+
mapping(bytes32 => ISharedMetadataBatch.SharedMetadataWithId) metadata;
18+
}
19+
20+
function sharedMetadataBatchStorage() internal pure returns (Data storage sharedMetadataBatchData) {
21+
bytes32 position = SHARED_METADATA_BATCH_STORAGE_POSITION;
22+
assembly {
23+
sharedMetadataBatchData.slot := position
24+
}
25+
}
26+
}
27+
28+
abstract contract SharedMetadataBatch is ISharedMetadataBatch {
29+
using EnumerableSet for EnumerableSet.Bytes32Set;
30+
31+
/// @notice Set shared metadata for NFTs
32+
function setSharedMetadata(SharedMetadataInfo calldata metadata, bytes32 _id) external {
33+
require(_canSetSharedMetadata(), "SharedMetadataBatch: cannot set shared metadata");
34+
_createSharedMetadata(metadata, _id);
35+
}
36+
37+
/// @notice Delete shared metadata for NFTs
38+
function deleteSharedMetadata(bytes32 _id) external {
39+
require(_canSetSharedMetadata(), "SharedMetadataBatch: cannot set shared metadata");
40+
require(_sharedMetadataBatchStorage().ids.remove(_id), "SharedMetadataBatch: shared metadata does not exist");
41+
42+
delete _sharedMetadataBatchStorage().metadata[_id];
43+
44+
emit SharedMetadataDeleted(_id);
45+
}
46+
47+
/// @notice Get all shared metadata
48+
function getAllSharedMetadata() external view returns (SharedMetadataWithId[] memory metadata) {
49+
bytes32[] memory ids = _sharedMetadataBatchStorage().ids.values();
50+
metadata = new SharedMetadataWithId[](ids.length);
51+
52+
for (uint256 i = 0; i < ids.length; i += 1) {
53+
metadata[i] = _sharedMetadataBatchStorage().metadata[ids[i]];
54+
}
55+
}
56+
57+
/// @dev Store shared metadata
58+
function _createSharedMetadata(SharedMetadataInfo calldata _metadata, bytes32 _id) internal {
59+
require(_sharedMetadataBatchStorage().ids.add(_id), "SharedMetadataBatch: shared metadata already exists");
60+
61+
_sharedMetadataBatchStorage().metadata[_id] = SharedMetadataWithId(_id, _metadata);
62+
63+
emit SharedMetadataUpdated(
64+
_id,
65+
_metadata.name,
66+
_metadata.description,
67+
_metadata.imageURI,
68+
_metadata.animationURI
69+
);
70+
}
71+
72+
/// @dev Token URI information getter
73+
function _getURIFromSharedMetadata(bytes32 id, uint256 tokenId) internal view returns (string memory) {
74+
SharedMetadataInfo memory info = _sharedMetadataBatchStorage().metadata[id].metadata;
75+
76+
return
77+
NFTMetadataRenderer.createMetadataEdition({
78+
name: info.name,
79+
description: info.description,
80+
imageURI: info.imageURI,
81+
animationURI: info.animationURI,
82+
tokenOfEdition: tokenId
83+
});
84+
}
85+
86+
/// @dev Get contract storage
87+
function _sharedMetadataBatchStorage() internal pure returns (SharedMetadataBatchStorage.Data storage data) {
88+
data = SharedMetadataBatchStorage.sharedMetadataBatchStorage();
89+
}
90+
91+
/// @dev Returns whether shared metadata can be set in the given execution context.
92+
function _canSetSharedMetadata() internal view virtual returns (bool);
93+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "../../eip/queryable/ERC721AStorage.sol";
5+
import "../../eip/queryable/ERC721A__Initializable.sol";
6+
7+
contract ERC721AQueryableInit is ERC721A__Initializable {
8+
function __ERC721A_init(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
9+
__ERC721A_init_unchained(name_, symbol_);
10+
}
11+
12+
function __ERC721A_init_unchained(string memory name_, string memory symbol_) internal onlyInitializingERC721A {
13+
ERC721AStorage.layout()._name = name_;
14+
ERC721AStorage.layout()._symbol = symbol_;
15+
ERC721AStorage.layout()._currentIndex = _startTokenId();
16+
}
17+
18+
function _startTokenId() internal view virtual returns (uint256) {
19+
return 0;
20+
}
21+
}

contracts/dynamic-contracts/init/PermissionsInit.sol

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ contract PermissionsInit {
77
event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);
88
event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);
99

10+
/// @dev Default admin role for all roles. Only accounts with this role can grant/revoke other roles.
11+
bytes32 internal constant DEFAULT_ADMIN_ROLE = 0x00;
12+
1013
/// @dev Sets up `role` for `account`
1114
function _setupRole(bytes32 role, address account) internal virtual {
1215
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
interface IRulesEngine {
5+
enum TokenType {
6+
ERC20,
7+
ERC721,
8+
ERC1155
9+
}
10+
11+
struct Rule {
12+
address token;
13+
TokenType tokenType;
14+
uint256 tokenId;
15+
uint256 balance;
16+
uint256 score;
17+
}
18+
19+
struct RuleWithId {
20+
uint256 ruleId;
21+
Rule rule;
22+
}
23+
24+
event RuleCreated(uint256 indexed ruleId, Rule rule);
25+
event RuleDeleted(uint256 indexed ruleId, Rule rule);
26+
27+
function getScore(address _tokenOwner) external view returns (uint256 score);
28+
29+
function getAllRules() external view returns (RuleWithId[] memory rules);
30+
31+
function createRule(Rule memory rule) external returns (uint256 ruleId);
32+
33+
function deleteRule(uint256 ruleId) external;
34+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// SPDX-License-Identifier: Apache 2.0
2+
pragma solidity ^0.8.10;
3+
4+
/// @author thirdweb
5+
6+
interface ISharedMetadataBatch {
7+
/// @notice Emitted when shared metadata is lazy minted.
8+
event SharedMetadataUpdated(
9+
bytes32 indexed id,
10+
string name,
11+
string description,
12+
string imageURI,
13+
string animationURI
14+
);
15+
16+
/// @notice Emitted when shared metadata is deleted.
17+
event SharedMetadataDeleted(bytes32 indexed id);
18+
19+
/**
20+
* @notice Structure for metadata shared across all tokens
21+
*
22+
* @param name Shared name of NFT in metadata
23+
* @param description Shared description of NFT in metadata
24+
* @param imageURI Shared URI of image to render for NFTs
25+
* @param animationURI Shared URI of animation to render for NFTs
26+
*/
27+
struct SharedMetadataInfo {
28+
string name;
29+
string description;
30+
string imageURI;
31+
string animationURI;
32+
}
33+
34+
struct SharedMetadataWithId {
35+
bytes32 id;
36+
SharedMetadataInfo metadata;
37+
}
38+
39+
/**
40+
* @notice Set shared metadata for NFTs
41+
* @param metadata common metadata for all tokens
42+
* @param id UID for the metadata
43+
*/
44+
function setSharedMetadata(SharedMetadataInfo calldata metadata, bytes32 id) external;
45+
46+
/**
47+
* @notice Delete shared metadata for NFTs
48+
* @param id UID for the metadata
49+
*/
50+
function deleteSharedMetadata(bytes32 id) external;
51+
52+
/**
53+
* @notice Get all shared metadata
54+
* @return metadata array of all shared metadata
55+
*/
56+
function getAllSharedMetadata() external view returns (SharedMetadataWithId[] memory metadata);
57+
}

0 commit comments

Comments
 (0)