Created
December 29, 2022 07:17
-
-
Save MatiFalcone/c0f6c5c82a59d8628160a5a9e372ccc4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: Apache-2.0 | |
pragma solidity ^0.8.11; | |
// ========== External imports ========== | |
import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol"; | |
import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; | |
import "@openzeppelin/contracts-upgradeable/interfaces/IERC2981Upgradeable.sol"; | |
// ========== Internal imports ========== | |
import "../interfaces/ISmartWallet.sol"; | |
import "../openzeppelin-presets/metatx/ERC2771ContextUpgradeable.sol"; | |
// ========== Features ========== | |
import "../extension/ContractMetadata.sol"; | |
import "../extension/Royalty.sol"; | |
import "../extension/ThirdOwnable.sol"; | |
import "../extension/PermissionsEnumerable.sol"; | |
import { TokenStore, ERC1155ReceiverUpgradeable, IERC1155ReceiverUpgradeable } from "../extension/TokenStore.sol"; | |
// ========== Proxy ========== | |
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | |
contract SmartWallet is | |
Initializable, | |
ContractMetadata, | |
Royalty, | |
ThirdOwnable, | |
PermissionsEnumerable, | |
TokenStore, | |
ReentrancyGuardUpgradeable, | |
ERC2771ContextUpgradeable, | |
ERC721EnumerableUpgradeable, | |
ISmartWallet, | |
UUPSUpgradeable | |
{ | |
/*/////////////////////////////////////////////////////////////// | |
State variables | |
//////////////////////////////////////////////////////////////*/ | |
bytes32 private constant MODULE_TYPE = bytes32("Multiwrap"); | |
uint256 private constant VERSION = 1; | |
/// @dev Only transfers to or from TRANSFER_ROLE holders are valid, when transfers are restricted. | |
bytes32 private constant TRANSFER_ROLE = keccak256("TRANSFER_ROLE"); | |
/// @dev Only MINTER_ROLE holders can wrap tokens, when wrapping is restricted. | |
bytes32 private constant MINTER_ROLE = keccak256("MINTER_ROLE"); | |
/// @dev Only UNWRAP_ROLE holders can unwrap tokens, when unwrapping is restricted. | |
bytes32 private constant UNWRAP_ROLE = keccak256("UNWRAP_ROLE"); | |
/// @dev Only assets with ASSET_ROLE can be wrapped, when wrapping is restricted to particular assets. | |
bytes32 private constant ASSET_ROLE = keccak256("ASSET_ROLE"); | |
/// @dev The next token ID of the NFT to mint. | |
uint256 public nextTokenIdToMint; | |
/*/////////////////////////////////////////////////////////////// | |
Constructor + initializer logic | |
//////////////////////////////////////////////////////////////*/ | |
/// @custom:oz-upgrades-unsafe-allow constructor | |
constructor(address _nativeTokenWrapper) TokenStore(_nativeTokenWrapper) initializer {} // 0x9c3C9283D3e44854697Cd22D3Faa240Cfb032889 | |
/// @dev Initiliazes the contract, like a constructor. | |
function initialize( | |
address _defaultAdmin, | |
string memory _name, | |
string memory _symbol, | |
string memory _contractURI, | |
address[] memory _trustedForwarders, | |
address _royaltyRecipient, | |
uint256 _royaltyBps | |
) external initializer { | |
// Initialize inherited contracts, most base-like -> most derived. | |
__ReentrancyGuard_init(); | |
__ERC2771Context_init(_trustedForwarders); | |
__ERC721_init(_name, _symbol); | |
// Initialize this contract's state. | |
_setupDefaultRoyaltyInfo(_royaltyRecipient, _royaltyBps); | |
_setupOwner(_defaultAdmin); | |
_setupContractURI(_contractURI); | |
_setupRole(DEFAULT_ADMIN_ROLE, _defaultAdmin); | |
_setupRole(MINTER_ROLE, _defaultAdmin); | |
_setupRole(TRANSFER_ROLE, _defaultAdmin); | |
// note: see `_beforeTokenTransfer` for TRANSFER_ROLE behaviour. | |
_setupRole(TRANSFER_ROLE, address(0)); | |
// note: see `onlyRoleWithSwitch` for UNWRAP_ROLE behaviour. | |
_setupRole(UNWRAP_ROLE, address(0)); | |
// note: see `onlyRoleWithSwitch` for UNWRAP_ROLE behaviour. | |
_setupRole(ASSET_ROLE, address(0)); | |
// note: mints empty Smart NFT | |
mintSmartNFT(_defaultAdmin); | |
} | |
///@dev required by the OZ UUPS module | |
function _authorizeUpgrade(address) internal override onlyOwner {} | |
/*/////////////////////////////////////////////////////////////// | |
Modifiers | |
//////////////////////////////////////////////////////////////*/ | |
modifier onlyRoleWithSwitch(bytes32 role) { | |
_checkRoleWithSwitch(role, _msgSender()); | |
_; | |
} | |
/*/////////////////////////////////////////////////////////////// | |
Generic contract logic | |
//////////////////////////////////////////////////////////////*/ | |
/// @dev Returns the type of the contract. | |
function contractType() external pure returns (bytes32) { | |
return MODULE_TYPE; | |
} | |
/// @dev Returns the version of the contract. | |
function contractVersion() external pure returns (uint8) { | |
return uint8(VERSION); | |
} | |
/// @dev Lets the contract receive ether to unwrap native tokens. | |
receive() external payable { | |
require(msg.sender == nativeTokenWrapper, "caller not native token wrapper."); | |
} | |
/*/////////////////////////////////////////////////////////////// | |
ERC 165 / 721 / 2981 logic | |
//////////////////////////////////////////////////////////////*/ | |
/// @dev Returns the URI for a given tokenId. | |
function tokenURI(uint256 _tokenId) public view override returns (string memory) { | |
return getUriOfBundle(_tokenId); | |
} | |
/// @dev See ERC 165 | |
function supportsInterface(bytes4 interfaceId) | |
public | |
view | |
virtual | |
override(ERC1155ReceiverUpgradeable, ERC721EnumerableUpgradeable, IERC165Upgradeable) | |
returns (bool) | |
{ | |
return | |
super.supportsInterface(interfaceId) || | |
interfaceId == type(IERC721Upgradeable).interfaceId || | |
interfaceId == type(IERC1155ReceiverUpgradeable).interfaceId || | |
interfaceId == type(IERC2981Upgradeable).interfaceId; | |
} | |
/*/////////////////////////////////////////////////////////////// | |
Minting logic | |
//////////////////////////////////////////////////////////////*/ | |
/// @dev Mint an empty ERC721 NFT that can later wrap multiple ERC1155, ERC721, ERC20 tokens. | |
function mintSmartNFT( | |
address _recipient | |
) public nonReentrant onlyRoleWithSwitch(MINTER_ROLE) returns (uint256 tokenId) { | |
Token[] memory noTokens; | |
tokenId = nextTokenIdToMint; | |
nextTokenIdToMint += 1; | |
_safeMint(_recipient, tokenId); | |
emit SmartWalletCreated(_msgSender(), _recipient, tokenId, noTokens); | |
} | |
/*/////////////////////////////////////////////////////////////// | |
Loading / Unloading logic | |
//////////////////////////////////////////////////////////////*/ | |
/// @dev Load multiple ERC1155, ERC721, ERC20 tokens into empty Smart NFT | |
function initialLoad( | |
Token[] calldata _tokensToLoad, | |
uint256 _smartNFTId, | |
string calldata _uriForLoadedToken | |
) external payable nonReentrant onlyRoleWithSwitch(MINTER_ROLE) { | |
require(getTokenCountOfBundle(_smartNFTId) == 0, "Smart NFT not empty. Call loadTokens instead."); | |
if (!hasRole(ASSET_ROLE, address(0))) { | |
for (uint256 i = 0; i < _tokensToLoad.length; i += 1) { | |
_checkRole(ASSET_ROLE, _tokensToLoad[i].assetContract); | |
} | |
} | |
// check that Smart NFT exists | |
require(_smartNFTId < nextTokenIdToMint, "Smart NFT DNE."); | |
_storeTokens(_msgSender(), _tokensToLoad, _uriForLoadedToken, _smartNFTId); | |
emit TokensLoaded(_msgSender(), _smartNFTId, _tokensToLoad); | |
} | |
/// @dev Load multiple ERC1155, ERC721, ERC20 tokens into a non-empty Smart NFT (bundle already created). | |
function loadTokens( | |
Token[] calldata _tokensToLoad, | |
uint256 _smartNFTId | |
) external payable nonReentrant onlyRoleWithSwitch(MINTER_ROLE) { | |
require(getTokenCountOfBundle(_smartNFTId) > 0, "Smart NFT is empty. Call initialLoad instead."); | |
require(_smartNFTId < nextTokenIdToMint, "Smart NFT DNE."); | |
require(_isApprovedOrOwner(_msgSender(), _smartNFTId), "caller not approved for loading this Smart NFT."); | |
if (!hasRole(ASSET_ROLE, address(0))) { | |
for (uint256 i = 0; i < _tokensToLoad.length; i += 1) { | |
_checkRole(ASSET_ROLE, _tokensToLoad[i].assetContract); | |
} | |
} | |
for (uint256 i = 0; i < _tokensToLoad.length; i += 1) { | |
_addTokenInBundle(_tokensToLoad[i], _smartNFTId); | |
_transferToken(_msgSender(), address(this), _tokensToLoad[i]); | |
} | |
emit TokensLoaded(_msgSender(), _smartNFTId, _tokensToLoad); | |
} | |
/// @dev Unload from Smart Wallet to retrieve one underlying ERC1155, ERC721, ERC20 token. | |
function unloadToken(uint256 _smartNFTId, Token calldata _tokenToRetrieve, address _recipient) external nonReentrant onlyRoleWithSwitch(UNWRAP_ROLE) { | |
require(_smartNFTId < nextTokenIdToMint, "Smart NFT DNE."); | |
require(_isApprovedOrOwner(_msgSender(), _smartNFTId), "caller not approved for unloading."); | |
_releaseToken(_recipient, _smartNFTId, _tokenToRetrieve); | |
emit TokenUnloaded(_msgSender(), _recipient, _smartNFTId, _tokenToRetrieve); | |
} | |
/// @dev Unload from Smart Wallet to retrieve all underlying ERC1155, ERC721, ERC20 tokens. | |
function unloadAll(uint256 _smartNFTId, address _recipient) external nonReentrant onlyRoleWithSwitch(UNWRAP_ROLE) { | |
require(_smartNFTId < nextTokenIdToMint, "Smart NFT DNE."); | |
require(_isApprovedOrOwner(_msgSender(), _smartNFTId), "caller not approved for unloading."); | |
_releaseTokens(_recipient, _smartNFTId); | |
emit AllTokensUnloaded(_msgSender(), _recipient, _smartNFTId); | |
} | |
/*/////////////////////////////////////////////////////////////// | |
Getter functions | |
//////////////////////////////////////////////////////////////*/ | |
/// @dev Returns the underlying contents of a wrapped NFT. | |
function getWrappedContents(uint256 _tokenId) external view returns (Token[] memory contents) { | |
uint256 total = getTokenCountOfBundle(_tokenId); | |
contents = new Token[](total); | |
for (uint256 i = 0; i < total; i += 1) { | |
contents[i] = getTokenOfBundle(_tokenId, i); | |
} | |
} | |
/*/////////////////////////////////////////////////////////////// | |
Internal functions | |
//////////////////////////////////////////////////////////////*/ | |
/// @dev Returns whether owner can be set in the given execution context. | |
function _canSetOwner() internal view override returns (bool) { | |
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender()); | |
} | |
/// @dev Returns whether royalty info can be set in the given execution context. | |
function _canSetRoyaltyInfo() internal view override returns (bool) { | |
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender()); | |
} | |
/// @dev Returns whether contract metadata can be set in the given execution context. | |
function _canSetContractURI() internal view override returns (bool) { | |
return hasRole(DEFAULT_ADMIN_ROLE, _msgSender()); | |
} | |
/*/////////////////////////////////////////////////////////////// | |
Miscellaneous | |
//////////////////////////////////////////////////////////////*/ | |
/** | |
* @dev See {ERC721-_beforeTokenTransfer}. | |
*/ | |
function _beforeTokenTransfer( | |
address from, | |
address to, | |
uint256 tokenId | |
) internal virtual { | |
super._beforeTokenTransfer(from, to, tokenId, 1); | |
// if transfer is restricted on the contract, we still want to allow burning and minting | |
if (!hasRole(TRANSFER_ROLE, address(0)) && from != address(0) && to != address(0)) { | |
require(hasRole(TRANSFER_ROLE, from) || hasRole(TRANSFER_ROLE, to), "!TRANSFER_ROLE"); | |
} | |
} | |
function _msgSender() | |
internal | |
view | |
virtual | |
override(ContextUpgradeable, ERC2771ContextUpgradeable) | |
returns (address sender) | |
{ | |
return ERC2771ContextUpgradeable._msgSender(); | |
} | |
function _msgData() | |
internal | |
view | |
virtual | |
override(ContextUpgradeable, ERC2771ContextUpgradeable) | |
returns (bytes calldata) | |
{ | |
return ERC2771ContextUpgradeable._msgData(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | |
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; | |
import "@openzeppelin/contracts-upgradeable/utils/CountersUpgradeable.sol"; | |
import "../proxy/SmartWalletProxy.sol"; | |
import "../multiwrap/SmartWallet.sol"; | |
contract SmartWalletFactory is Initializable, OwnableUpgradeable { | |
using CountersUpgradeable for CountersUpgradeable.Counter; | |
mapping(uint256 => address) private smartWallets; | |
CountersUpgradeable.Counter private counter; | |
string private version; | |
address implementation; | |
function initialize(address smartWallet, string memory _version) public initializer { | |
//_transferOwnership(tx.origin); | |
version = _version; | |
implementation = smartWallet; | |
} | |
function versionContract() public view returns (string memory) { | |
return version; | |
} | |
function createSmartWallet( | |
string memory _name, | |
string memory _symbol, | |
string memory _contractURI, | |
address[] memory _trustedForwarders, | |
address _royaltyRecipient, | |
uint256 _royaltyBps | |
) public { | |
SmartWalletProxy newSmartWallet = new SmartWalletProxy(address(implementation), abi.encodeWithSelector(SmartWallet(payable(address(0))).initialize.selector, msg.sender, _name, _symbol, _contractURI, _trustedForwarders, _royaltyRecipient, _royaltyBps)); | |
uint256 currentCounter = counter.current(); | |
smartWallets[currentCounter] = address(newSmartWallet); | |
counter.increment(); | |
} | |
function getSmartWalletAddress(uint256 id) public view returns(address) { | |
return smartWallets[id]; | |
} | |
function getCurrentCounter() public view returns(uint256) { | |
return counter.current(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts/proxy/Proxy.sol"; | |
import "@openzeppelin/contracts/access/Ownable.sol"; | |
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; | |
// Kept for backwards compatibility with older versions of Hardhat and Truffle plugins. | |
contract SmartWalletProxy is ERC1967Proxy, Ownable { | |
constructor(address _logic, bytes memory _data) ERC1967Proxy(_logic, _data) { | |
transferOwnership(tx.origin); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment