Skip to content

Commit 9247a05

Browse files
kumaryash90jakeloo
andauthored
Pack refactor downsized (#229)
* update design doc for Pack * openPack out of gas -- fuzz test * [M-1] Lost pack tokens result in permanently locked assets * [G-2] Reduce function calls in openPack * [L-3] supportsInterface() may prevent ERC721 tokens from transferring to Pack * [H-1] Reward selection exploits * optimize openPack * pack refactor: adding more tokens to a pack * add tests * wip: reduce size * cremoved stuffs * restore roles, token-bundle getters; remove withdrawUnclaimedAssets, packTimestamps * immutable forwarder; edit revert strings; * cleanup * update tests * eoa-only forwarder * run prettier * update tests * minor changes: supports interface, role vars, check recipient in addPackContents * update tests * run prettier Co-authored-by: Jake Loo <[email protected]>
1 parent 1e241b7 commit 9247a05

File tree

14 files changed

+978
-168
lines changed

14 files changed

+978
-168
lines changed

contracts/ForwarderEOAOnly.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
pragma solidity ^0.8.11;
3+
4+
import "./openzeppelin-presets/metatx/MinimalForwarderEOAOnly.sol";
5+
6+
/*
7+
* @dev Minimal forwarder for GSNv2
8+
*/
9+
contract ForwarderEOAOnly is MinimalForwarderEOAOnly {
10+
// solhint-disable-next-line no-empty-blocks
11+
constructor() MinimalForwarderEOAOnly() {}
12+
}

contracts/extension/TokenBundle.sol

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ interface IERC165 {
1717

1818
abstract contract TokenBundle is ITokenBundle {
1919
/// @dev Mapping from bundle UID => bundle info.
20-
mapping(uint256 => BundleInfo) private bundle;
20+
mapping(uint256 => BundleInfo) public bundle;
2121

2222
/// @dev Returns the total number of assets in a particular bundle.
2323
function getTokenCountOfBundle(uint256 _bundleId) public view returns (uint256) {
@@ -38,8 +38,8 @@ abstract contract TokenBundle is ITokenBundle {
3838
function _createBundle(Token[] calldata _tokensToBind, uint256 _bundleId) internal {
3939
uint256 targetCount = _tokensToBind.length;
4040

41-
require(targetCount > 0, "TokenBundle: no tokens to bind.");
42-
require(bundle[_bundleId].count == 0, "TokenBundle: existent at bundleId");
41+
require(targetCount > 0, "no tokens to bind");
42+
require(bundle[_bundleId].count == 0, "existent at bundleId");
4343

4444
for (uint256 i = 0; i < targetCount; i += 1) {
4545
_checkTokenType(_tokensToBind[i]);
@@ -50,8 +50,8 @@ abstract contract TokenBundle is ITokenBundle {
5050
}
5151

5252
/// @dev Lets the calling contract update a bundle, by passing in a list of tokens and a unique id.
53-
function _updateBundle(Token[] calldata _tokensToBind, uint256 _bundleId) internal {
54-
require(_tokensToBind.length > 0, "TokenBundle: no tokens to bind.");
53+
function _updateBundle(Token[] memory _tokensToBind, uint256 _bundleId) internal {
54+
require(_tokensToBind.length > 0, "no tokens to bind");
5555

5656
uint256 currentCount = bundle[_bundleId].count;
5757
uint256 targetCount = _tokensToBind.length;
@@ -84,7 +84,7 @@ abstract contract TokenBundle is ITokenBundle {
8484
uint256 _bundleId,
8585
uint256 _index
8686
) internal {
87-
require(_index < bundle[_bundleId].count, "TokenBundle: index DNE.");
87+
require(_index < bundle[_bundleId].count, "index DNE");
8888
_checkTokenType(_tokenToBind);
8989
bundle[_bundleId].tokens[_index] = _tokenToBind;
9090
}
@@ -93,32 +93,32 @@ abstract contract TokenBundle is ITokenBundle {
9393
function _checkTokenType(Token memory _token) internal view {
9494
if (_token.tokenType == TokenType.ERC721) {
9595
try IERC165(_token.assetContract).supportsInterface(0x80ac58cd) returns (bool supported721) {
96-
require(supported721, "Asset doesn't match TokenType");
96+
require(supported721, "!TokenType");
9797
} catch {
98-
revert("Asset doesn't match TokenType");
98+
revert("!TokenType");
9999
}
100100
} else if (_token.tokenType == TokenType.ERC1155) {
101101
try IERC165(_token.assetContract).supportsInterface(0xd9b67a26) returns (bool supported1155) {
102-
require(supported1155, "Asset doesn't match TokenType");
102+
require(supported1155, "!TokenType");
103103
} catch {
104-
revert("Asset doesn't match TokenType");
104+
revert("!TokenType");
105105
}
106106
} else if (_token.tokenType == TokenType.ERC20) {
107107
if (_token.assetContract != CurrencyTransferLib.NATIVE_TOKEN) {
108108
// 0x36372b07
109109
try IERC165(_token.assetContract).supportsInterface(0x80ac58cd) returns (bool supported721) {
110-
require(!supported721, "Asset doesn't match TokenType");
110+
require(!supported721, "!TokenType");
111111

112112
try IERC165(_token.assetContract).supportsInterface(0xd9b67a26) returns (bool supported1155) {
113-
require(!supported1155, "Asset doesn't match TokenType");
113+
require(!supported1155, "!TokenType");
114114
} catch Error(string memory) {} catch {}
115115
} catch Error(string memory) {} catch {}
116116
}
117117
}
118118
}
119119

120120
/// @dev Lets the calling contract set/update the uri of a particular bundle.
121-
function _setUriOfBundle(string calldata _uri, uint256 _bundleId) internal {
121+
function _setUriOfBundle(string memory _uri, uint256 _bundleId) internal {
122122
bundle[_bundleId].uri = _uri;
123123
}
124124

contracts/extension/TokenStore.sol

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,6 @@ import "../lib/CurrencyTransferLib.sol";
2222
*/
2323

2424
contract TokenStore is TokenBundle, ERC721Holder, ERC1155Holder {
25-
/// @dev The address interpreted as native token of the chain.
26-
address public constant NATIVE_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
27-
2825
/// @dev The address of the native token wrapper contract.
2926
address internal immutable nativeTokenWrapper;
3027

@@ -36,7 +33,7 @@ contract TokenStore is TokenBundle, ERC721Holder, ERC1155Holder {
3633
function _storeTokens(
3734
address _tokenOwner,
3835
Token[] calldata _tokens,
39-
string calldata _uriForTokens,
36+
string memory _uriForTokens,
4037
uint256 _idForTokens
4138
) internal {
4239
_createBundle(_tokens, _idForTokens);

contracts/interfaces/IPack.sol

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,10 @@ interface IPack is ITokenBundle {
2424
}
2525

2626
/// @notice Emitted when a set of packs is created.
27-
event PackCreated(
28-
uint256 indexed packId,
29-
address indexed packCreator,
30-
address recipient,
31-
uint256 totalPacksCreated
32-
);
27+
event PackCreated(uint256 indexed packId, address recipient, uint256 totalPacksCreated);
28+
29+
/// @notice Emitted when more packs are minted for a packId.
30+
event PackUpdated(uint256 indexed packId, address recipient, uint256 totalPacksCreated);
3331

3432
/// @notice Emitted when a pack is opened.
3533
event PackOpened(
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// SPDX-License-Identifier: MIT
2+
// OpenZeppelin Contracts (last updated v4.5.0) (metatx/MinimalForwarder.sol)
3+
4+
pragma solidity ^0.8.0;
5+
6+
import "../utils/cryptography/ECDSA.sol";
7+
import "../utils/cryptography/EIP712.sol";
8+
9+
/**
10+
* @dev Simple minimal forwarder to be used together with an ERC2771 compatible contract. See {ERC2771Context}.
11+
*/
12+
contract MinimalForwarderEOAOnly is EIP712 {
13+
using ECDSA for bytes32;
14+
15+
struct ForwardRequest {
16+
address from;
17+
address to;
18+
uint256 value;
19+
uint256 gas;
20+
uint256 nonce;
21+
bytes data;
22+
}
23+
24+
bytes32 private constant _TYPEHASH =
25+
keccak256("ForwardRequest(address from,address to,uint256 value,uint256 gas,uint256 nonce,bytes data)");
26+
27+
mapping(address => uint256) private _nonces;
28+
29+
constructor() EIP712("MinimalForwarderEOAOnly", "0.0.1") {}
30+
31+
function getNonce(address from) public view returns (uint256) {
32+
return _nonces[from];
33+
}
34+
35+
function verify(ForwardRequest calldata req, bytes calldata signature) public view returns (bool) {
36+
address signer = _hashTypedDataV4(
37+
keccak256(abi.encode(_TYPEHASH, req.from, req.to, req.value, req.gas, req.nonce, keccak256(req.data)))
38+
).recover(signature);
39+
return _nonces[req.from] == req.nonce && signer == req.from;
40+
}
41+
42+
function execute(ForwardRequest calldata req, bytes calldata signature)
43+
public
44+
payable
45+
returns (bool, bytes memory)
46+
{
47+
require(msg.sender == tx.origin, "not EOA");
48+
require(verify(req, signature), "MinimalForwarder: signature does not match request");
49+
_nonces[req.from] = req.nonce + 1;
50+
51+
(bool success, bytes memory returndata) = req.to.call{ gas: req.gas, value: req.value }(
52+
abi.encodePacked(req.data, req.from)
53+
);
54+
55+
// Validate that the relayer has sent enough gas for the call.
56+
// See https://ronan.eth.link/blog/ethereum-gas-dangers/
57+
if (gasleft() <= req.gas / 63) {
58+
// We explicitly trigger invalid opcode to consume all gas and bubble-up the effects, since
59+
// neither revert or assert consume all gas since Solidity 0.8.0
60+
// https://docs.soliditylang.org/en/v0.8.0/control-structures.html#panic-via-assert-and-error-via-require
61+
assembly {
62+
invalid()
63+
}
64+
}
65+
66+
return (success, returndata);
67+
}
68+
}

0 commit comments

Comments
 (0)