• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

delvtech / hyperdrive / 13083371547

01 Feb 2025 12:55AM UTC coverage: 89.268% (-0.2%) from 89.489%
13083371547

Pull #1238

github

jalextowle
Added the remaining tests
Pull Request #1238: ERC1155 Compatibility

69 of 80 new or added lines in 5 files covered. (86.25%)

1 existing line in 1 file now uncovered.

3036 of 3401 relevant lines covered (89.27%)

325758.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.65
/contracts/src/internal/HyperdriveMultiToken.sol
1
// SPDX-License-Identifier: Apache-2.0
2
pragma solidity 0.8.24;
3

4
import { IERC1155Receiver } from "openzeppelin/interfaces/IERC1155Receiver.sol";
5
import { IHyperdrive } from "../interfaces/IHyperdrive.sol";
6
import { IHyperdriveEvents } from "../interfaces/IHyperdriveEvents.sol";
7
import { HyperdriveBase } from "./HyperdriveBase.sol";
8

9
/// @author DELV
10
/// @title HyperdriveMultiToken
11
/// @notice Implements the MultiToken accounting that Hyperdrive uses to track
12
///         user's positions. MultiToken maintains a set of balances and
13
///         approvals for a list of sub-tokens specified by an asset ID. This
14
///         token is mostly ERC1155 compliant; however, we remove on transfer
15
///         callbacks and safe transfer because of the risk of external calls to
16
///         untrusted code.
17
/// @dev Our architecture maintains ERC20 compatibility by allowing users to
18
///      access their balances and approvals through ERC20 forwarding contracts
19
///      deployed by the registered forwarder factory. To ensure that only the
20
///      ERC20 forwarders can call the bridge endpoints, we verify that the
21
///      create2 pre-image of the caller address is the ERC20 forwarder bytecode
22
///      and the token ID.
23
/// @custom:disclaimer The language used in this code is for coding convenience
24
///                    only, and is not intended to, and does not, have any
25
///                    particular legal or regulatory significance.
26
abstract contract HyperdriveMultiToken is IHyperdriveEvents, HyperdriveBase {
27
    /// @notice This modifier checks the caller is the create2 validated
28
    ///         ERC20 bridge.
29
    /// @param _tokenID The internal token identifier.
30
    modifier onlyLinker(uint256 _tokenID) {
31
        // If the caller does not match the address hash, we revert because it
32
        // is not allowed to access permissioned methods.
33
        if (msg.sender != _deriveForwarderAddress(_tokenID)) {
5✔
34
            revert IHyperdrive.InvalidERC20Bridge();
1✔
35
        }
36

37
        // Execute the following function.
38
        _;
39
    }
40

41
    /// @dev Transfers several assets from one account to another.
42
    /// @param _from The source account.
43
    /// @param _to The destination account.
44
    /// @param _ids The array of token ids of the asset to transfer.
45
    /// @param _values The amount of each token to transfer.
46
    function _batchTransferFrom(
47
        address _from,
48
        address _to,
49
        uint256[] calldata _ids,
50
        uint256[] calldata _values
51
    ) internal {
52
        // Checks for inconsistent addresses.
53
        if (_from == address(0) || _to == address(0)) {
10✔
54
            revert IHyperdrive.RestrictedZeroAddress();
2✔
55
        }
56

57
        // Check for inconsistent length.
58
        if (_ids.length != _values.length) {
8✔
59
            revert IHyperdrive.BatchInputLengthMismatch();
2✔
60
        }
61

62
        // Call internal transfer for each asset.
63
        for (uint256 i = 0; i < _ids.length; i++) {
6✔
64
            _transferFrom(_ids[i], _from, _to, _values[i], msg.sender);
8✔
65
        }
66
    }
67

68
    /// @dev Performs the actual transfer logic.
69
    /// @param _tokenID The token identifier.
70
    /// @param _from The address whose balance will be reduced.
71
    /// @param _to The address whose balance will be increased.
72
    /// @param _amount The amount of token to move.
73
    /// @param _caller The msg.sender or the caller of the ERC20Forwarder.
74
    function _transferFrom(
75
        uint256 _tokenID,
76
        address _from,
77
        address _to,
78
        uint256 _amount,
79
        address _caller
80
    ) internal {
81
        // Checks for inconsistent addresses.
82
        if (_from == address(0) || _to == address(0)) {
256✔
83
            revert IHyperdrive.RestrictedZeroAddress();
2✔
84
        }
85

86
        // If the transaction sender is calling no need for further validation.
87
        if (_caller != _from) {
254✔
88
            // Or if the transaction sender can access all user assets, no need
89
            // for more validation.
90
            if (!_isApprovedForAll[_from][_caller]) {
86✔
91
                // Finally we load the per asset approval.
92
                uint256 approved = _perTokenApprovals[_tokenID][_from][_caller];
67✔
93
                // If it is not an infinite approval
94
                if (approved != type(uint256).max) {
67✔
95
                    // Then we subtract the amount the caller wants to use
96
                    // from how much they can use, reverting on underflow.
97
                    // NOTE: This reverts without message for unapproved callers
98
                    // when debugging that's the likely source of any mystery
99
                    // reverts.
100
                    _perTokenApprovals[_tokenID][_from][_caller] -= _amount;
52✔
101
                }
102
            }
103
        }
104

105
        // Reaching this point implies the transfer is authorized so we remove
106
        // from the source and add to the destination.
107
        _balanceOf[_tokenID][_from] -= _amount;
235✔
108
        _balanceOf[_tokenID][_to] += _amount;
191✔
109
        emit TransferSingle(_caller, _from, _to, _tokenID, _amount);
191✔
110
    }
111

112
    /// @dev Safely transfers tokens, checking if recipient is a contract and
113
    ///      can handle ERC1155 tokens.
114
    /// @param _from The source address.
115
    /// @param _to The destination address.
116
    /// @param _id The token identifier.
117
    /// @param _amount The amount to transfer.
118
    /// @param _data Additional data to pass to recipient if it's a contract.
119
    function _safeTransferFrom(
120
        address _from,
121
        address _to,
122
        uint256 _id,
123
        uint256 _amount,
124
        bytes calldata _data
125
    ) internal nonReentrant {
126
        // Perform the regular transfer first.
127
        _transferFrom(_id, _from, _to, _amount, msg.sender);
27✔
128

129
        // If the destination is a contract, verify it can handle ERC1155 tokens.
130
        if (_to.code.length > 0) {
25✔
131
            try
132
                IERC1155Receiver(_to).onERC1155Received(
23✔
133
                    msg.sender,
134
                    _from,
135
                    _id,
136
                    _amount,
137
                    _data
138
                )
139
            returns (bytes4 response) {
22✔
140
                if (response != IERC1155Receiver.onERC1155Received.selector) {
22✔
141
                    revert IHyperdrive.ERC1155InvalidReceiver();
1✔
142
                }
NEW
143
            } catch Error(string memory reason) {
×
NEW
144
                revert(reason);
×
145
            } catch {
1✔
146
                revert IHyperdrive.ERC1155InvalidReceiver();
1✔
147
            }
148
        }
149
    }
150

151
    /// @dev Safely transfers multiple tokens in a batch.
152
    /// @param _from The source address.
153
    /// @param _to The destination address.
154
    /// @param _ids Array of token identifiers.
155
    /// @param _amounts Array of amounts to transfer for each token.
156
    /// @param _data Additional data to pass to recipient if it's a contract.
157
    function _safeBatchTransferFrom(
158
        address _from,
159
        address _to,
160
        uint256[] calldata _ids,
161
        uint256[] calldata _amounts,
162
        bytes memory _data
163
    ) internal nonReentrant {
164
        // Perform the regular batch transfer first.
165
        _batchTransferFrom(_from, _to, _ids, _amounts);
10✔
166

167
        // If the destination is a contract, verify it can handle ERC1155 tokens
168
        if (_to.code.length > 0) {
5✔
169
            try
170
                IERC1155Receiver(_to).onERC1155BatchReceived(
3✔
171
                    msg.sender,
172
                    _from,
173
                    _ids,
174
                    _amounts,
175
                    _data
176
                )
177
            returns (bytes4 response) {
2✔
178
                if (
179
                    response != IERC1155Receiver.onERC1155BatchReceived.selector
2✔
180
                ) {
1✔
181
                    revert IHyperdrive.ERC1155InvalidReceiver();
1✔
182
                }
NEW
183
            } catch Error(string memory reason) {
×
NEW
184
                revert(reason);
×
185
            } catch {
1✔
186
                revert IHyperdrive.ERC1155InvalidReceiver();
1✔
187
            }
188
        }
189
    }
190

191
    /// @notice Sets the approval for a sub-token.
192
    /// @param _tokenID The asset to approve the use of.
193
    /// @param _operator The address who will be able to use the tokens.
194
    /// @param _amount The max tokens the approved person can use, setting to
195
    ///        uint256.max will cause the value to never decrement (saving gas
196
    ///        on transfer).
197
    /// @param _caller The eth address which initiated the approval call.
198
    function _setApproval(
199
        uint256 _tokenID,
200
        address _operator,
201
        uint256 _amount,
202
        address _caller
203
    ) internal {
204
        _perTokenApprovals[_tokenID][_caller][_operator] = _amount;
193✔
205

206
        // Emit an event to track approval.
207
        emit Approval(_caller, _operator, _amount);
193✔
208
    }
209

210
    /// @notice Minting function to create tokens.
211
    /// @param _tokenID The asset type to create.
212
    /// @param _to The address whose balance to increase.
213
    /// @param _amount The number of tokens to create.
214
    /// @dev Must be used from inheriting contracts.
215
    function _mint(
216
        uint256 _tokenID,
217
        address _to,
218
        uint256 _amount
219
    ) internal virtual {
220
        _balanceOf[_tokenID][_to] += _amount;
199,683✔
221
        _totalSupply[_tokenID] += _amount;
199,683✔
222

223
        // Emit an event to track minting.
224
        emit TransferSingle(msg.sender, address(0), _to, _tokenID, _amount);
199,683✔
225
    }
226

227
    /// @notice Burning function to remove tokens.
228
    /// @param _tokenID The asset type to remove.
229
    /// @param _from The address whose balance to decrease.
230
    /// @param _amount The number of tokens to remove.
231
    /// @dev Must be used from inheriting contracts.
232
    function _burn(uint256 _tokenID, address _from, uint256 _amount) internal {
233
        // Check to see if the balance is sufficient. If it isn't, throw an
234
        // insufficient balance error.
235
        if (_balanceOf[_tokenID][_from] < _amount) {
126,248✔
236
            revert IHyperdrive.InsufficientBalance();
4✔
237
        }
238

239
        // Decrement from the source and supply.
240
        unchecked {
241
            _balanceOf[_tokenID][_from] -= _amount;
126,244✔
242
        }
243
        _totalSupply[_tokenID] -= _amount;
126,244✔
244

245
        // Emit an event to track burning.
246
        emit TransferSingle(msg.sender, _from, address(0), _tokenID, _amount);
126,244✔
247
    }
248

249
    /// @dev Allows a caller who is not the owner of an account to execute the
250
    ///      functionality of 'approve' for all assets with the owners signature.
251
    /// @param _domainSeparator The EIP712 domain separator for this contract.
252
    /// @param _permitTypehash The EIP712 typehash for the permit data.
253
    /// @param _owner The owner of the account which is having the new approval set.
254
    /// @param _spender The address which will be allowed to spend owner's tokens.
255
    /// @param _approved A boolean of the approval status to set to.
256
    /// @param _deadline The timestamp which the signature must be submitted by
257
    ///        _to be valid.
258
    /// @param _v Extra ECDSA data which allows public key recovery from
259
    ///        _signature assumed to be 27 or 28.
260
    /// @param _r The r component of the ECDSA signature.
261
    /// @param _s The s component of the ECDSA signature.
262
    /// @dev The signature for this function follows EIP 712 standard and should
263
    ///      be generated with the eth_signTypedData JSON RPC call instead of
264
    ///      the eth_sign JSON RPC call. If using out of date parity signing
265
    ///      libraries the v component may need to be adjusted. Also it is very
266
    ///      rare but possible for v to be other values, those values are not
267
    ///      supported.
268
    function _permitForAll(
269
        bytes32 _domainSeparator,
270
        bytes32 _permitTypehash,
271
        address _owner,
272
        address _spender,
273
        bool _approved,
274
        uint256 _deadline,
275
        uint8 _v,
276
        bytes32 _r,
277
        bytes32 _s
278
    ) internal {
279
        // Require that the signature is not expired.
280
        if (block.timestamp > _deadline) {
8✔
281
            revert IHyperdrive.ExpiredDeadline();
1✔
282
        }
283

284
        // Require that the owner is not zero.
285
        if (_owner == address(0)) {
7✔
286
            revert IHyperdrive.RestrictedZeroAddress();
1✔
287
        }
288

289
        // Check that the signature is valid and recovers to the owner.
290
        bytes32 structHash = keccak256(
6✔
291
            abi.encodePacked(
292
                "\x19\x01",
293
                _domainSeparator,
294
                keccak256(
295
                    abi.encode(
296
                        _permitTypehash,
297
                        _owner,
298
                        _spender,
299
                        _approved,
300
                        _nonces[_owner],
301
                        _deadline
302
                    )
303
                )
304
            )
305
        );
306
        address signer = ecrecover(structHash, _v, _r, _s);
6✔
307
        if (signer != _owner) {
6✔
308
            revert IHyperdrive.InvalidSignature();
2✔
309
        }
310

311
        // Increment the signature nonce.
312
        unchecked {
313
            ++_nonces[_owner];
4✔
314
        }
315

316
        // Set the state.
317
        _isApprovedForAll[_owner][_spender] = _approved;
4✔
318

319
        // Emit an event to track approval.
320
        emit ApprovalForAll(_owner, _spender, _approved);
4✔
321
    }
322

323
    /// @notice Derive the ERC20 forwarder address for a provided `tokenId`.
324
    /// @param _tokenId Token Id of the token whose forwarder contract address
325
    ///        need to derived.
326
    /// @return Address of the ERC20 forwarder contract.
327
    function _deriveForwarderAddress(
328
        uint256 _tokenId
329
    ) internal view returns (address) {
330
        // Get the salt which is used by the deploying contract.
331
        bytes32 salt = keccak256(abi.encode(address(this), _tokenId));
106✔
332

333
        // Perform the hash which determines the address of a create2 deployment.
334
        bytes32 addressBytes = keccak256(
106✔
335
            abi.encodePacked(
336
                bytes1(0xff),
337
                _linkerFactory,
338
                salt,
339
                _linkerCodeHash
340
            )
341
        );
342
        return address(uint160(uint256(addressBytes)));
106✔
343
    }
344
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc