Skip to content

Commit ea1a044

Browse files
authored
Smart wallets: TWAccount and TWDynamic account (#363)
* Make all top level functions virtual on ContractKit bases * pkg update * Make MAX_BPS private in Marketplace plugins * Add sample AA implementation * Add design objectives for TWAccount * Update TWAccountFactory * Emit event on Account creation * Update TWAccount * Fix compile errors * WIP: TWRouter has everything * docs update * delete dummy contract * Two separate wallets: regular and dynamic * Create TWDynamicAccount factory * WIP: TWAccount benchmark tests * Update createAccount: return instead of revert for used salt * Add receive function to TWAccount * Fix TWDynamicAccount.initialize * comment out test that works with new entrypoint * Remove unused test events * Fix file casing * fix casing * fix casing again * camelcase entrypoint everywhere * casing * fix build issues
1 parent 49887e2 commit ea1a044

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+6200
-54
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
/// @author thirdweb
5+
6+
import "../../extension/interface/IContractMetadata.sol";
7+
8+
/**
9+
* @author thirdweb.com
10+
*
11+
* @title Contract Metadata
12+
* @notice Thirdweb's `ContractMetadata` is a contract extension for any base contracts. It lets you set a metadata URI
13+
* for you contract.
14+
* Additionally, `ContractMetadata` is necessary for NFT contracts that want royalties to get distributed on OpenSea.
15+
*/
16+
17+
library ContractMetadataStorage {
18+
bytes32 public constant CONTRACT_METADATA_STORAGE_POSITION = keccak256("contract.metadata.storage");
19+
20+
struct Data {
21+
/// @notice Returns the contract metadata URI.
22+
string contractURI;
23+
}
24+
25+
function contractMetadataStorage() internal pure returns (Data storage contractMetadataData) {
26+
bytes32 position = CONTRACT_METADATA_STORAGE_POSITION;
27+
assembly {
28+
contractMetadataData.slot := position
29+
}
30+
}
31+
}
32+
33+
abstract contract ContractMetadata is IContractMetadata {
34+
/**
35+
* @notice Lets a contract admin set the URI for contract-level metadata.
36+
* @dev Caller should be authorized to setup contractURI, e.g. contract admin.
37+
* See {_canSetContractURI}.
38+
* Emits {ContractURIUpdated Event}.
39+
*
40+
* @param _uri keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
41+
*/
42+
function setContractURI(string memory _uri) external override {
43+
if (!_canSetContractURI()) {
44+
revert("Not authorized");
45+
}
46+
47+
_setupContractURI(_uri);
48+
}
49+
50+
/// @dev Lets a contract admin set the URI for contract-level metadata.
51+
function _setupContractURI(string memory _uri) internal {
52+
ContractMetadataStorage.Data storage data = ContractMetadataStorage.contractMetadataStorage();
53+
string memory prevURI = data.contractURI;
54+
data.contractURI = _uri;
55+
56+
emit ContractURIUpdated(prevURI, _uri);
57+
}
58+
59+
/// @notice Returns the contract metadata URI.
60+
function contractURI() public view virtual override returns (string memory) {
61+
ContractMetadataStorage.Data storage data = ContractMetadataStorage.contractMetadataStorage();
62+
return data.contractURI;
63+
}
64+
65+
/// @dev Returns whether contract metadata can be set in the given execution context.
66+
function _canSetContractURI() internal view virtual returns (bool);
67+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// SPDX-License-Identifier: Apache 2.0
2+
pragma solidity ^0.8.0;
3+
4+
import "../../lib/TWAddress.sol";
5+
6+
library InitStorage {
7+
/// @dev The location of the storage of the entrypoint contract's data.
8+
bytes32 constant INIT_STORAGE_POSITION = keccak256("init.storage");
9+
10+
/// @dev Layout of the entrypoint contract's storage.
11+
struct Data {
12+
uint8 initialized;
13+
bool initializing;
14+
}
15+
16+
/// @dev Returns the entrypoint contract's data at the relevant storage location.
17+
function initStorage() internal pure returns (Data storage initData) {
18+
bytes32 position = INIT_STORAGE_POSITION;
19+
assembly {
20+
initData.slot := position
21+
}
22+
}
23+
}
24+
25+
abstract contract Initializable {
26+
/**
27+
* @dev Triggered when the contract has been initialized or reinitialized.
28+
*/
29+
event Initialized(uint8 version);
30+
31+
/**
32+
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
33+
* `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.
34+
*/
35+
modifier initializer() {
36+
InitStorage.Data storage data = InitStorage.initStorage();
37+
uint8 _initialized = data.initialized;
38+
bool _initializing = data.initializing;
39+
40+
bool isTopLevelCall = !_initializing;
41+
require(
42+
(isTopLevelCall && _initialized < 1) || (!TWAddress.isContract(address(this)) && _initialized == 1),
43+
"Initializable: contract is already initialized"
44+
);
45+
data.initialized = 1;
46+
if (isTopLevelCall) {
47+
data.initializing = true;
48+
}
49+
_;
50+
if (isTopLevelCall) {
51+
data.initializing = false;
52+
emit Initialized(1);
53+
}
54+
}
55+
56+
/**
57+
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
58+
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
59+
* used to initialize parent contracts.
60+
*
61+
* `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original
62+
* initialization step. This is essential to configure modules that are added through upgrades and that require
63+
* initialization.
64+
*
65+
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
66+
* a contract, executing them in the right order is up to the developer or operator.
67+
*/
68+
modifier reinitializer(uint8 version) {
69+
InitStorage.Data storage data = InitStorage.initStorage();
70+
uint8 _initialized = data.initialized;
71+
bool _initializing = data.initializing;
72+
73+
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
74+
data.initialized = version;
75+
data.initializing = true;
76+
_;
77+
data.initializing = false;
78+
emit Initialized(version);
79+
}
80+
81+
/**
82+
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
83+
* {initializer} and {reinitializer} modifiers, directly or indirectly.
84+
*/
85+
modifier onlyInitializing() {
86+
InitStorage.Data storage data = InitStorage.initStorage();
87+
require(data.initializing, "Initializable: contract is not initializing");
88+
_;
89+
}
90+
91+
/**
92+
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
93+
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
94+
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
95+
* through proxies.
96+
*/
97+
function _disableInitializers() internal virtual {
98+
InitStorage.Data storage data = InitStorage.initStorage();
99+
uint8 _initialized = data.initialized;
100+
bool _initializing = data.initializing;
101+
102+
require(!_initializing, "Initializable: contract is initializing");
103+
if (_initialized < type(uint8).max) {
104+
data.initialized = type(uint8).max;
105+
emit Initialized(type(uint8).max);
106+
}
107+
}
108+
}
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.0;
3+
4+
/// @author thirdweb
5+
6+
import "../../extension/interface/IPermissions.sol";
7+
import "../../lib/TWStrings.sol";
8+
9+
/**
10+
* @title Permissions
11+
* @dev This contracts provides extending-contracts with role-based access control mechanisms
12+
*/
13+
14+
library PermissionsStorage {
15+
bytes32 public constant PERMISSIONS_STORAGE_POSITION = keccak256("permissions.storage");
16+
17+
struct Data {
18+
/// @dev Map from keccak256 hash of a role => a map from address => whether address has role.
19+
mapping(bytes32 => mapping(address => bool)) _hasRole;
20+
/// @dev Map from keccak256 hash of a role to role admin. See {getRoleAdmin}.
21+
mapping(bytes32 => bytes32) _getRoleAdmin;
22+
}
23+
24+
function permissionsStorage() internal pure returns (Data storage permissionsData) {
25+
bytes32 position = PERMISSIONS_STORAGE_POSITION;
26+
assembly {
27+
permissionsData.slot := position
28+
}
29+
}
30+
}
31+
32+
contract Permissions is IPermissions {
33+
/// @dev Default admin role for all roles. Only accounts with this role can grant/revoke other roles.
34+
bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;
35+
36+
/// @dev Modifier that checks if an account has the specified role; reverts otherwise.
37+
modifier onlyRole(bytes32 role) {
38+
_checkRole(role, _msgSender());
39+
_;
40+
}
41+
42+
/**
43+
* @notice Checks whether an account has a particular role.
44+
* @dev Returns `true` if `account` has been granted `role`.
45+
*
46+
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
47+
* @param account Address of the account for which the role is being checked.
48+
*/
49+
function hasRole(bytes32 role, address account) public view override returns (bool) {
50+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
51+
return data._hasRole[role][account];
52+
}
53+
54+
/**
55+
* @notice Checks whether an account has a particular role;
56+
* role restrictions can be swtiched on and off.
57+
*
58+
* @dev Returns `true` if `account` has been granted `role`.
59+
* Role restrictions can be swtiched on and off:
60+
* - If address(0) has ROLE, then the ROLE restrictions
61+
* don't apply.
62+
* - If address(0) does not have ROLE, then the ROLE
63+
* restrictions will apply.
64+
*
65+
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
66+
* @param account Address of the account for which the role is being checked.
67+
*/
68+
function hasRoleWithSwitch(bytes32 role, address account) public view returns (bool) {
69+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
70+
if (!data._hasRole[role][address(0)]) {
71+
return data._hasRole[role][account];
72+
}
73+
74+
return true;
75+
}
76+
77+
/**
78+
* @notice Returns the admin role that controls the specified role.
79+
* @dev See {grantRole} and {revokeRole}.
80+
* To change a role's admin, use {_setRoleAdmin}.
81+
*
82+
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
83+
*/
84+
function getRoleAdmin(bytes32 role) external view override returns (bytes32) {
85+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
86+
return data._getRoleAdmin[role];
87+
}
88+
89+
/**
90+
* @notice Grants a role to an account, if not previously granted.
91+
* @dev Caller must have admin role for the `role`.
92+
* Emits {RoleGranted Event}.
93+
*
94+
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
95+
* @param account Address of the account to which the role is being granted.
96+
*/
97+
function grantRole(bytes32 role, address account) public virtual override {
98+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
99+
_checkRole(data._getRoleAdmin[role], _msgSender());
100+
if (data._hasRole[role][account]) {
101+
revert("Can only grant to non holders");
102+
}
103+
_setupRole(role, account);
104+
}
105+
106+
/**
107+
* @notice Revokes role from an account.
108+
* @dev Caller must have admin role for the `role`.
109+
* Emits {RoleRevoked Event}.
110+
*
111+
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
112+
* @param account Address of the account from which the role is being revoked.
113+
*/
114+
function revokeRole(bytes32 role, address account) public virtual override {
115+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
116+
_checkRole(data._getRoleAdmin[role], _msgSender());
117+
_revokeRole(role, account);
118+
}
119+
120+
/**
121+
* @notice Revokes role from the account.
122+
* @dev Caller must have the `role`, with caller being the same as `account`.
123+
* Emits {RoleRevoked Event}.
124+
*
125+
* @param role keccak256 hash of the role. e.g. keccak256("TRANSFER_ROLE")
126+
* @param account Address of the account from which the role is being revoked.
127+
*/
128+
function renounceRole(bytes32 role, address account) public virtual override {
129+
if (_msgSender() != account) {
130+
revert("Can only renounce for self");
131+
}
132+
_revokeRole(role, account);
133+
}
134+
135+
/// @dev Sets `adminRole` as `role`'s admin role.
136+
function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
137+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
138+
bytes32 previousAdminRole = data._getRoleAdmin[role];
139+
data._getRoleAdmin[role] = adminRole;
140+
emit RoleAdminChanged(role, previousAdminRole, adminRole);
141+
}
142+
143+
/// @dev Sets up `role` for `account`
144+
function _setupRole(bytes32 role, address account) internal virtual {
145+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
146+
data._hasRole[role][account] = true;
147+
emit RoleGranted(role, account, _msgSender());
148+
}
149+
150+
/// @dev Revokes `role` from `account`
151+
function _revokeRole(bytes32 role, address account) internal virtual {
152+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
153+
_checkRole(role, account);
154+
delete data._hasRole[role][account];
155+
emit RoleRevoked(role, account, _msgSender());
156+
}
157+
158+
/// @dev Checks `role` for `account`. Reverts with a message including the required role.
159+
function _checkRole(bytes32 role, address account) internal view virtual {
160+
PermissionsStorage.Data storage data = PermissionsStorage.permissionsStorage();
161+
if (!data._hasRole[role][account]) {
162+
revert(
163+
string(
164+
abi.encodePacked(
165+
"Permissions: account ",
166+
TWStrings.toHexString(uint160(account), 20),
167+
" is missing role ",
168+
TWStrings.toHexString(uint256(role), 32)
169+
)
170+
)
171+
);
172+
}
173+
}
174+
175+
/// @dev Checks `role` for `account`. Reverts with a message including the required role.
176+
function _checkRoleWithSwitch(bytes32 role, address account) internal view virtual {
177+
if (!hasRoleWithSwitch(role, account)) {
178+
revert(
179+
string(
180+
abi.encodePacked(
181+
"Permissions: account ",
182+
TWStrings.toHexString(uint160(account), 20),
183+
" is missing role ",
184+
TWStrings.toHexString(uint256(role), 32)
185+
)
186+
)
187+
);
188+
}
189+
}
190+
191+
function _msgSender() internal view virtual returns (address sender) {
192+
return msg.sender;
193+
}
194+
195+
function _msgData() internal view virtual returns (bytes calldata) {
196+
return msg.data;
197+
}
198+
}

0 commit comments

Comments
 (0)