Skip to content

Commit 2bb75ad

Browse files
authored
Update EvolvingNFT (#458)
* Add multicall to evolving nft * Missing author tags * Add two types of rules * add ability to override rules engine * Use RulesEngine as an independent extension
1 parent 5bd29c0 commit 2bb75ad

File tree

7 files changed

+200
-80
lines changed

7 files changed

+200
-80
lines changed
Lines changed: 89 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
11
// SPDX-License-Identifier: Apache-2.0
22
pragma solidity ^0.8.11;
33

4+
/// @author thirdweb
5+
46
import "../../extension/interface/IRulesEngine.sol";
57

68
import "../../eip/interface/IERC20.sol";
79
import "../../eip/interface/IERC721.sol";
810
import "../../eip/interface/IERC1155.sol";
911

12+
import "../../openzeppelin-presets/utils/structs/EnumerableSet.sol";
13+
1014
library RulesEngineStorage {
1115
bytes32 public constant RULES_ENGINE_STORAGE_POSITION = keccak256("rules.engine.storage");
1216

1317
struct Data {
14-
uint256 nextRuleId;
15-
mapping(uint256 => IRulesEngine.RuleWithId) rules;
18+
address rulesEngineOverride;
19+
EnumerableSet.Bytes32Set ids;
20+
mapping(bytes32 => IRulesEngine.RuleWithId) rules;
1621
}
1722

1823
function rulesEngineStorage() internal pure returns (Data storage rulesEngineData) {
@@ -24,61 +29,77 @@ library RulesEngineStorage {
2429
}
2530

2631
abstract contract RulesEngine is IRulesEngine {
32+
using EnumerableSet for EnumerableSet.Bytes32Set;
33+
2734
/*///////////////////////////////////////////////////////////////
2835
View functions
2936
//////////////////////////////////////////////////////////////*/
3037

3138
function getScore(address _tokenOwner) public view returns (uint256 score) {
32-
uint256 len = _rulesEngineStorage().nextRuleId;
39+
address engineOverride = getRulesEngineOverride();
40+
if (engineOverride != address(0)) {
41+
return IRulesEngine(engineOverride).getScore(_tokenOwner);
42+
}
43+
44+
bytes32[] memory ids = _rulesEngineStorage().ids.values();
45+
uint256 len = ids.length;
3346

3447
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-
}
48+
RuleWithId memory rule = _rulesEngineStorage().rules[ids[i]];
49+
score += _getScoreForRule(_tokenOwner, rule);
5050
}
5151
}
5252

5353
function getAllRules() external view returns (RuleWithId[] memory rules) {
54-
uint256 len = _rulesEngineStorage().nextRuleId;
55-
uint256 count = 0;
54+
bytes32[] memory ids = _rulesEngineStorage().ids.values();
55+
uint256 len = ids.length;
56+
57+
rules = new RuleWithId[](len);
5658

5759
for (uint256 i = 0; i < len; i += 1) {
58-
if (_rulesEngineStorage().rules[i].rule.token != address(0)) {
59-
count++;
60-
}
60+
rules[i] = _rulesEngineStorage().rules[ids[i]];
6161
}
62+
}
6263

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-
}
64+
function getRulesEngineOverride() public view returns (address rulesEngineAddress) {
65+
rulesEngineAddress = _rulesEngineStorage().rulesEngineOverride;
7066
}
7167

7268
/*///////////////////////////////////////////////////////////////
7369
External functions
7470
//////////////////////////////////////////////////////////////*/
7571

76-
function createRule(Rule memory rule) external returns (uint256 ruleId) {
72+
function createRuleMulitiplicative(RuleTypeMultiplicative memory rule) external returns (bytes32 ruleId) {
73+
require(_canSetRules(), "RulesEngine: cannot set rules");
74+
75+
ruleId = keccak256(
76+
abi.encodePacked(rule.token, rule.tokenType, rule.tokenId, rule.scorePerOwnedToken, RuleType.Multiplicative)
77+
);
78+
_createRule(
79+
RuleWithId(
80+
ruleId,
81+
rule.token,
82+
rule.tokenType,
83+
rule.tokenId,
84+
0, // balance
85+
rule.scorePerOwnedToken,
86+
RuleType.Multiplicative
87+
)
88+
);
89+
}
90+
91+
function createRuleThreshold(RuleTypeThreshold memory rule) external returns (bytes32 ruleId) {
7792
require(_canSetRules(), "RulesEngine: cannot set rules");
78-
ruleId = _createRule(rule);
93+
94+
ruleId = keccak256(
95+
abi.encodePacked(rule.token, rule.tokenType, rule.tokenId, rule.balance, rule.score, RuleType.Threshold)
96+
);
97+
_createRule(
98+
RuleWithId(ruleId, rule.token, rule.tokenType, rule.tokenId, rule.balance, rule.score, RuleType.Threshold)
99+
);
79100
}
80101

81-
function deleteRule(uint256 _ruleId) external {
102+
function deleteRule(bytes32 _ruleId) external {
82103
require(_canSetRules(), "RulesEngine: cannot set rules");
83104
_deleteRule(_ruleId);
84105
}
@@ -87,20 +108,50 @@ abstract contract RulesEngine is IRulesEngine {
87108
Internal functions
88109
//////////////////////////////////////////////////////////////*/
89110

90-
function _createRule(Rule memory _rule) internal returns (uint256 ruleId) {
91-
ruleId = _rulesEngineStorage().nextRuleId++;
92-
_rulesEngineStorage().rules[ruleId] = RuleWithId(ruleId, _rule);
111+
function _getScoreForRule(address _tokenOwner, RuleWithId memory _rule) internal view returns (uint256 score) {
112+
uint256 balance = 0;
93113

94-
emit RuleCreated(ruleId, _rule);
114+
if (_rule.tokenType == TokenType.ERC20) {
115+
balance = IERC20(_rule.token).balanceOf(_tokenOwner);
116+
} else if (_rule.tokenType == TokenType.ERC721) {
117+
balance = IERC721(_rule.token).balanceOf(_tokenOwner);
118+
} else if (_rule.tokenType == TokenType.ERC1155) {
119+
balance = IERC1155(_rule.token).balanceOf(_tokenOwner, _rule.tokenId);
120+
}
121+
122+
if (_rule.ruleType == RuleType.Threshold) {
123+
if (balance >= _rule.balance) {
124+
score = _rule.score;
125+
}
126+
} else if (_rule.ruleType == RuleType.Multiplicative) {
127+
score = balance * _rule.score;
128+
}
95129
}
96130

97-
function _deleteRule(uint256 _ruleId) internal {
131+
function _createRule(RuleWithId memory _rule) internal {
132+
require(_rulesEngineStorage().ids.add(_rule.ruleId), "RulesEngine: rule already exists");
133+
_rulesEngineStorage().rules[_rule.ruleId] = _rule;
134+
emit RuleCreated(_rule.ruleId, _rule);
135+
}
136+
137+
function _deleteRule(bytes32 _ruleId) internal {
138+
require(_rulesEngineStorage().ids.remove(_ruleId), "RulesEngine: rule already exists");
98139
delete _rulesEngineStorage().rules[_ruleId];
140+
emit RuleDeleted(_ruleId);
141+
}
142+
143+
function setRulesEngineOverride(address _rulesEngineAddress) external {
144+
require(_canOverrieRulesEngine(), "RulesEngine: cannot override rules engine");
145+
_rulesEngineStorage().rulesEngineOverride = _rulesEngineAddress;
146+
147+
emit RulesEngineOverriden(_rulesEngineAddress);
99148
}
100149

101150
function _rulesEngineStorage() internal pure returns (RulesEngineStorage.Data storage data) {
102151
data = RulesEngineStorage.rulesEngineStorage();
103152
}
104153

105154
function _canSetRules() internal view virtual returns (bool);
155+
156+
function _canOverrieRulesEngine() internal view virtual returns (bool);
106157
}

contracts/dynamic-contracts/extension/SharedMetadataBatch.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: Apache-2.0
22
pragma solidity ^0.8.11;
33

4+
/// @author thirdweb
5+
46
import "../../lib/NFTMetadataRendererLib.sol";
57
import "../../extension/interface/ISharedMetadataBatch.sol";
68
import "../../openzeppelin-presets/utils/EnumerableSet.sol";

contracts/extension/interface/IRulesEngine.sol

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,27 +8,51 @@ interface IRulesEngine {
88
ERC1155
99
}
1010

11-
struct Rule {
11+
enum RuleType {
12+
Threshold,
13+
Multiplicative
14+
}
15+
16+
struct RuleTypeThreshold {
1217
address token;
1318
TokenType tokenType;
1419
uint256 tokenId;
1520
uint256 balance;
1621
uint256 score;
1722
}
1823

24+
struct RuleTypeMultiplicative {
25+
address token;
26+
TokenType tokenType;
27+
uint256 tokenId;
28+
uint256 scorePerOwnedToken;
29+
}
30+
1931
struct RuleWithId {
20-
uint256 ruleId;
21-
Rule rule;
32+
bytes32 ruleId;
33+
address token;
34+
TokenType tokenType;
35+
uint256 tokenId;
36+
uint256 balance;
37+
uint256 score;
38+
RuleType ruleType;
2239
}
2340

24-
event RuleCreated(uint256 indexed ruleId, Rule rule);
25-
event RuleDeleted(uint256 indexed ruleId, Rule rule);
41+
event RuleCreated(bytes32 indexed ruleId, RuleWithId rule);
42+
event RuleDeleted(bytes32 indexed ruleId);
43+
event RulesEngineOverriden(address indexed newRulesEngine);
2644

2745
function getScore(address _tokenOwner) external view returns (uint256 score);
2846

2947
function getAllRules() external view returns (RuleWithId[] memory rules);
3048

31-
function createRule(Rule memory rule) external returns (uint256 ruleId);
49+
function getRulesEngineOverride() external view returns (address rulesEngineAddress);
50+
51+
function createRuleMulitiplicative(RuleTypeMultiplicative memory rule) external returns (bytes32 ruleId);
52+
53+
function createRuleThreshold(RuleTypeThreshold memory rule) external returns (bytes32 ruleId);
54+
55+
function deleteRule(bytes32 ruleId) external;
3256

33-
function deleteRule(uint256 ruleId) external;
57+
function setRulesEngineOverride(address _rulesEngineAddress) external;
3458
}

contracts/unaudited/evolving-nfts/EvolvingNFT.sol

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pragma solidity ^0.8.11;
1414

1515
import "lib/dynamic-contracts/src/presets/BaseRouter.sol";
1616

17+
import "../../extension/Multicall.sol";
1718
import "../../dynamic-contracts/extension/Initializable.sol";
1819
import "../../dynamic-contracts/init/ContractMetadataInit.sol";
1920
import "../../dynamic-contracts/init/RoyaltyInit.sol";
@@ -27,6 +28,7 @@ import "../../dynamic-contracts/init/DefaultOperatorFiltererInit.sol";
2728
contract EvolvingNFT is
2829
Initializable,
2930
BaseRouter,
31+
Multicall,
3032
ERC721AQueryableInit,
3133
ERC2771ContextInit,
3234
ContractMetadataInit,

contracts/unaudited/evolving-nfts/EvolvingNFTLogic.sol

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import "../../dynamic-contracts/extension/Ownable.sol";
3434
import "../../dynamic-contracts/extension/Permissions.sol";
3535
import "../../dynamic-contracts/extension/Drop.sol";
3636
import "../../dynamic-contracts/extension/SharedMetadataBatch.sol";
37-
import "../../dynamic-contracts/extension/RulesEngine.sol";
37+
import { RulesEngine } from "../../dynamic-contracts/extension/RulesEngine.sol";
3838

3939
// OpenSea operator filter
4040
import "../../extension/DefaultOperatorFiltererUpgradeable.sol";
@@ -45,7 +45,6 @@ contract EvolvingNFTLogic is
4545
PrimarySale,
4646
Ownable,
4747
SharedMetadataBatch,
48-
RulesEngine,
4948
Drop,
5049
ERC2771ContextUpgradeable,
5150
DefaultOperatorFiltererUpgradeable,
@@ -84,7 +83,15 @@ contract EvolvingNFTLogic is
8483
}
8584

8685
// Get score
87-
uint256 score = getScore(ownerOf(_tokenId));
86+
address owner = ownerOf(_tokenId);
87+
uint256 score = 0;
88+
89+
address engine = RulesEngine(address(this)).getRulesEngineOverride();
90+
if (engine != address(0)) {
91+
score = RulesEngine(engine).getScore(owner);
92+
} else {
93+
score = RulesEngine(address(this)).getScore(owner);
94+
}
8895

8996
// Get the target ID i.e. `start` of the range that the score falls into.
9097
bytes32[] memory ids = _sharedMetadataBatchStorage().ids.values();
@@ -190,11 +197,6 @@ contract EvolvingNFTLogic is
190197
return _hasRole(MINTER_ROLE, _msgSender());
191198
}
192199

193-
/// @dev Returns whether the rules of the contract can be set in the given execution context.
194-
function _canSetRules() internal view virtual override returns (bool) {
195-
return _hasRole(MINTER_ROLE, _msgSender());
196-
}
197-
198200
/// @dev Returns whether operator restriction can be set in the given execution context.
199201
function _canSetOperatorRestriction() internal virtual override returns (bool) {
200202
return _hasRole(DEFAULT_ADMIN_ROLE, _msgSender());
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
/// @author thirdweb
5+
6+
import { PermissionsStorage } from "../../../dynamic-contracts/extension/Permissions.sol";
7+
import { RulesEngine } from "../../../dynamic-contracts/extension/RulesEngine.sol";
8+
9+
contract RulesEngineExtension is RulesEngine {
10+
/// @dev Returns whether the rules of the contract can be set in the given execution context.
11+
function _canSetRules() internal view virtual override returns (bool) {
12+
return _hasRole(keccak256("MINTER_ROLE"), msg.sender);
13+
}
14+
15+
/// @dev Returns whether the rules engine used by the contract can be overriden in the given execution context.
16+
function _canOverrieRulesEngine() internal view virtual override returns (bool) {
17+
// DEFAULT_ADMIN_ROLE
18+
return _hasRole(0x00, msg.sender);
19+
}
20+
21+
/// @dev Checks whether an account has a particular role.
22+
function _hasRole(bytes32 _role, address _account) internal view returns (bool) {
23+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
24+
return data._hasRole[_role][_account];
25+
}
26+
}

0 commit comments

Comments
 (0)