From 34488c92bb894d4fd2eec46d6b75793022b7e585 Mon Sep 17 00:00:00 2001 From: Akash Gianchandani Date: Wed, 27 Sep 2023 22:14:21 +0530 Subject: [PATCH] feat(contracts): add new forwarder contracts WIN-506 --- contracts/CloneFactory.sol | 2 +- contracts/ERC20Interface.sol | 2 +- contracts/IForwarder.sol | 2 +- contracts/NewForwarder.sol | 332 ++++++++++++++++++++++++++++++ contracts/NewForwarderFactory.sol | 50 +++++ contracts/TransferHelper.sol | 2 +- 6 files changed, 386 insertions(+), 4 deletions(-) create mode 100644 contracts/NewForwarder.sol create mode 100644 contracts/NewForwarderFactory.sol diff --git a/contracts/CloneFactory.sol b/contracts/CloneFactory.sol index 3609ea4..ea3e7a8 100644 --- a/contracts/CloneFactory.sol +++ b/contracts/CloneFactory.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // from https://github.com/optionality/clone-factory -pragma solidity 0.8.10; +pragma solidity 0.8.15; /* The MIT License (MIT) diff --git a/contracts/ERC20Interface.sol b/contracts/ERC20Interface.sol index 350dd5c..5684924 100644 --- a/contracts/ERC20Interface.sol +++ b/contracts/ERC20Interface.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.10; +pragma solidity 0.8.15; /** * Contract that exposes the needed erc20 token functions diff --git a/contracts/IForwarder.sol b/contracts/IForwarder.sol index ff33e71..3a9ab74 100644 --- a/contracts/IForwarder.sol +++ b/contracts/IForwarder.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.8.0; +pragma solidity 0.8.15; import '@openzeppelin/contracts/utils/introspection/IERC165.sol'; diff --git a/contracts/NewForwarder.sol b/contracts/NewForwarder.sol new file mode 100644 index 0000000..0fd1d0a --- /dev/null +++ b/contracts/NewForwarder.sol @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.15; +import '@openzeppelin/contracts/token/ERC1155/IERC1155.sol'; +import '@openzeppelin/contracts/token/ERC721/IERC721.sol'; +import '@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol'; +import '@openzeppelin/contracts/token/ERC1155/utils/ERC1155Receiver.sol'; +import './ERC20Interface.sol'; +import './TransferHelper.sol'; +import './IForwarder.sol'; + +/** + * Contract that will forward any incoming Ether to the parent address of the contract + * + */ +contract NewForwarder is IERC721Receiver, ERC1155Receiver, IForwarder { + // Address to which any funds sent to this contract will be forwarded + address public parentAddress; + address public feeAddress; + mapping(address => bool) public whiteListedAddresses; + bool public autoFlush721 = true; + bool public autoFlush1155 = true; + + event ForwarderDeposited(address from, uint256 value, bytes data); + + /** + * Initialize the contract, and sets the destination address to that of the parent address + */ + function init ( + address _parentAddress, + address _feeAddress, + bool _autoFlush721, + bool _autoFlush1155 + ) external onlyUninitialized { + parentAddress = _parentAddress; + feeAddress = _feeAddress; + + whiteListedAddresses[parentAddress] = true; + whiteListedAddresses[feeAddress] = true; + uint256 value = address(this).balance; + + // set whether we want to automatically flush erc721/erc1155 tokens or not + autoFlush721 = _autoFlush721; + autoFlush1155 = _autoFlush1155; + + if (value == 0) { + return; + } + + (bool success, ) = parentAddress.call{ value: value }(''); + require(success, 'Flush failed'); + + // NOTE: since we are forwarding on initialization, + // we don't have the context of the original sender. + // We still emit an event about the forwarding but set + // the sender to the forwarder itself + emit ForwarderDeposited(address(this), value, msg.data); + } + + /** + * Modifier that will execute internal code block only if the sender is the whitelisted address + */ + modifier onlyWhitelistedAddress { + require(whiteListedAddresses[msg.sender], 'Address not in whitelist'); + _; + } + + /** + * Modifier that will execute internal code block only if the contract has not been initialized yet + */ + modifier onlyUninitialized { + require(parentAddress == address(0x0), 'Already initialized'); + _; + } + + /** + * Default function; Gets called when data is sent but does not match any other function + */ + fallback() external payable { + flush(); + } + + /** + * Default function; Gets called when Ether is deposited with no data, and forwards it to the parent address + */ + receive() external payable { + flush(); + } + + /** + * @inheritdoc IForwarder + */ + function setAutoFlush721(bool autoFlush) + external + virtual + override + onlyWhitelistedAddress + { + autoFlush721 = autoFlush; + } + + /** + * @inheritdoc IForwarder + */ + function setAutoFlush1155(bool autoFlush) + external + virtual + override + onlyWhitelistedAddress + { + autoFlush1155 = autoFlush; + } + + /** + * ERC721 standard callback function for when a ERC721 is transfered. The forwarder will send the nft + * to the base wallet once the nft contract invokes this method after transfering the nft. + * + * @param _operator The address which called `safeTransferFrom` function + * @param _from The address of the sender + * @param _tokenId The token id of the nft + * @param data Additional data with no specified format, sent in call to `_to` + */ + function onERC721Received( + address _operator, + address _from, + uint256 _tokenId, + bytes memory data + ) external virtual override returns (bytes4) { + if (autoFlush721) { + IERC721 instance = IERC721(msg.sender); + require( + instance.supportsInterface(type(IERC721).interfaceId), + 'The caller does not support the ERC721 interface' + ); + // this won't work for ERC721 re-entrancy + instance.safeTransferFrom(address(this), parentAddress, _tokenId, data); + } + + return this.onERC721Received.selector; + } + + function callFromWhitelistedAddress( + address target, + uint256 value, + bytes calldata data + ) external onlyWhitelistedAddress returns (bytes memory) { + (bool success, bytes memory returnedData) = target.call{ value: value }( + data + ); + require(success, 'Whitelisted address call execution failed'); + + return returnedData; + } + + /** + * @inheritdoc IERC1155Receiver + */ + function onERC1155Received( + address _operator, + address _from, + uint256 id, + uint256 value, + bytes calldata data + ) external virtual override returns (bytes4) { + IERC1155 instance = IERC1155(msg.sender); + require( + instance.supportsInterface(type(IERC1155).interfaceId), + 'The caller does not support the IERC1155 interface' + ); + + if (autoFlush1155) { + instance.safeTransferFrom(address(this), parentAddress, id, value, data); + } + + return this.onERC1155Received.selector; + } + + /** + * @inheritdoc IERC1155Receiver + */ + function onERC1155BatchReceived( + address _operator, + address _from, + uint256[] calldata ids, + uint256[] calldata values, + bytes calldata data + ) external virtual override returns (bytes4) { + IERC1155 instance = IERC1155(msg.sender); + require( + instance.supportsInterface(type(IERC1155).interfaceId), + 'The caller does not support the IERC1155 interface' + ); + + if (autoFlush1155) { + instance.safeBatchTransferFrom( + address(this), + parentAddress, + ids, + values, + data + ); + } + + return this.onERC1155BatchReceived.selector; + } + + /** + * @inheritdoc IForwarder + */ + function flushTokens(address tokenContractAddress) + external + virtual + override + onlyWhitelistedAddress + { + ERC20Interface instance = ERC20Interface(tokenContractAddress); + address forwarderAddress = address(this); + uint256 forwarderBalance = instance.balanceOf(forwarderAddress); + if (forwarderBalance == 0) { + return; + } + + TransferHelper.safeTransfer( + tokenContractAddress, + parentAddress, + forwarderBalance + ); + } + + /** + * @inheritdoc IForwarder + */ + function flushERC721Token(address tokenContractAddress, uint256 tokenId) + external + virtual + override + onlyWhitelistedAddress + { + IERC721 instance = IERC721(tokenContractAddress); + require( + instance.supportsInterface(type(IERC721).interfaceId), + 'The tokenContractAddress does not support the ERC721 interface' + ); + + address ownerAddress = instance.ownerOf(tokenId); + instance.transferFrom(ownerAddress, parentAddress, tokenId); + } + + /** + * @inheritdoc IForwarder + */ + function flushERC1155Tokens(address tokenContractAddress, uint256 tokenId) + external + virtual + override + onlyWhitelistedAddress + { + IERC1155 instance = IERC1155(tokenContractAddress); + require( + instance.supportsInterface(type(IERC1155).interfaceId), + 'The caller does not support the IERC1155 interface' + ); + + address forwarderAddress = address(this); + uint256 forwarderBalance = instance.balanceOf(forwarderAddress, tokenId); + + instance.safeTransferFrom( + forwarderAddress, + parentAddress, + tokenId, + forwarderBalance, + '' + ); + } + + /** + * @inheritdoc IForwarder + */ + function batchFlushERC1155Tokens( + address tokenContractAddress, + uint256[] calldata tokenIds + ) external virtual override onlyWhitelistedAddress { + IERC1155 instance = IERC1155(tokenContractAddress); + require( + instance.supportsInterface(type(IERC1155).interfaceId), + 'The caller does not support the IERC1155 interface' + ); + + address forwarderAddress = address(this); + uint256[] memory amounts = new uint256[](tokenIds.length); + for (uint256 i = 0; i < tokenIds.length; i++) { + amounts[i] = instance.balanceOf(forwarderAddress, tokenIds[i]); + } + + instance.safeBatchTransferFrom( + forwarderAddress, + parentAddress, + tokenIds, + amounts, + '' + ); + } + + /** + * Flush the entire balance of the contract to the parent address. + */ + function flush() public { + uint256 value = address(this).balance; + + if (value == 0) { + return; + } + + (bool success, ) = parentAddress.call{ value: value }(''); + require(success, 'Flush failed'); + emit ForwarderDeposited(msg.sender, value, msg.data); + } + + /** + * @inheritdoc IERC165 + */ + function supportsInterface(bytes4 interfaceId) + public + virtual + override(ERC1155Receiver, IERC165) + view + returns (bool) + { + return + interfaceId == type(IForwarder).interfaceId || + super.supportsInterface(interfaceId); + } +} \ No newline at end of file diff --git a/contracts/NewForwarderFactory.sol b/contracts/NewForwarderFactory.sol new file mode 100644 index 0000000..b81a076 --- /dev/null +++ b/contracts/NewForwarderFactory.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.15; +import './NewForwarder.sol'; +import './CloneFactory.sol'; + +contract ForwarderFactory1 is CloneFactory { + address public implementationAddress; + + event ForwarderCreated( + address newForwarderAddress, + address parentAddress, + address feeAddress, + bool shouldAutoFlushERC721, + bool shouldAutoFlushERC1155 + ); + + constructor(address _implementationAddress) { + implementationAddress = _implementationAddress; + } + + function createForwarder(address parent, address feeAddress, bytes32 salt) external { + this.createForwarder(parent, feeAddress, salt, true, true); + } + + function createForwarder( + address parent, + address feeAddress, + bytes32 salt, + bool shouldAutoFlushERC721, + bool shouldAutoFlushERC1155 + ) external { + // include the signers in the salt so any contract deployed to a given address must have the same signers + bytes32 finalSalt = keccak256(abi.encodePacked(parent, salt)); + + address payable clone = createClone(implementationAddress, finalSalt); + Forwarder(clone).init( + parent, + feeAddress, + shouldAutoFlushERC721, + shouldAutoFlushERC1155 + ); + emit ForwarderCreated( + clone, + parent, + feeAddress, + shouldAutoFlushERC721, + shouldAutoFlushERC1155 + ); + } +} diff --git a/contracts/TransferHelper.sol b/contracts/TransferHelper.sol index d83159e..e4e2961 100644 --- a/contracts/TransferHelper.sol +++ b/contracts/TransferHelper.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later // source: https://github.com/Uniswap/solidity-lib/blob/master/contracts/libraries/TransferHelper.sol -pragma solidity 0.8.10; +pragma solidity 0.8.15; import '@openzeppelin/contracts/utils/Address.sol';