-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(contracts): add new forwarder contracts
- Loading branch information
1 parent
bebf6c7
commit 0923ba1
Showing
4 changed files
with
1,498 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,334 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity 0.8.20; | ||
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 UpdatedForwarder is IERC721Receiver, ERC1155Receiver, IForwarder { | ||
// Address to which any funds sent to this contract will be forwarded | ||
address public parentAddress; | ||
address public feeAddress; | ||
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 { | ||
require(_parentAddress != address(0x0), 'Invalid address'); | ||
parentAddress = _parentAddress; | ||
require(_feeAddress != address(0x0), 'Invalid address'); | ||
feeAddress = _feeAddress; | ||
|
||
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 from the allowed addresses | ||
*/ | ||
modifier onlyAllowedAddress() { | ||
require( | ||
msg.sender == parentAddress || msg.sender == feeAddress, | ||
'Address is not allowed' | ||
); | ||
_; | ||
} | ||
|
||
/** | ||
* 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 | ||
onlyAllowedAddress | ||
{ | ||
autoFlush721 = autoFlush; | ||
} | ||
|
||
/** | ||
* @inheritdoc IForwarder | ||
*/ | ||
function setAutoFlush1155(bool autoFlush) | ||
external | ||
virtual | ||
override | ||
onlyAllowedAddress | ||
{ | ||
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 callFromAllowedAddress( | ||
address target, | ||
uint256 value, | ||
bytes calldata data | ||
) external onlyAllowedAddress returns (bytes memory) { | ||
(bool success, bytes memory returnedData) = target.call{ value: value }( | ||
data | ||
); | ||
require(success, 'Allowed 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 | ||
onlyAllowedAddress | ||
{ | ||
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 | ||
onlyAllowedAddress | ||
{ | ||
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 | ||
onlyAllowedAddress | ||
{ | ||
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 onlyAllowedAddress { | ||
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 | ||
view | ||
virtual | ||
override(ERC1155Receiver, IERC165) | ||
returns (bool) | ||
{ | ||
return | ||
interfaceId == type(IForwarder).interfaceId || | ||
super.supportsInterface(interfaceId); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity 0.8.20; | ||
import './UpdatedForwarder.sol'; | ||
import './CloneFactory.sol'; | ||
|
||
contract UpdatedForwarderFactory 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); | ||
UpdatedForwarder(clone).init( | ||
parent, | ||
feeAddress, | ||
shouldAutoFlushERC721, | ||
shouldAutoFlushERC1155 | ||
); | ||
emit ForwarderCreated( | ||
clone, | ||
parent, | ||
feeAddress, | ||
shouldAutoFlushERC721, | ||
shouldAutoFlushERC1155 | ||
); | ||
} | ||
} |
Oops, something went wrong.