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

delvtech / hyperdrive / 13148119986

05 Feb 2025 12:05AM UTC coverage: 89.245% (-0.2%) from 89.489%
13148119986

push

github

web-flow
ERC1155 Compatibility (#1238)

* Added the missing ERC1155 functions for compatability

* Made the Hyperdrive multi token ERC1155 compatible

* Fixed the zap tests

* Updated the MultiToken tests for ERC1155 compatibility

* Added more tests

* Added the remaining tests

* Addressed review feedback from @Sean329

* Fixed the broken tests

* Fixed the code size issue

70 of 82 new or added lines in 6 files covered. (85.37%)

1 existing line in 1 file now uncovered.

3037 of 3403 relevant lines covered (89.24%)

325484.76 hits per line

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

93.75
/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
        uint256 length = _ids.length;
6✔
64
        for (uint256 i = 0; i < length; i++) {
6✔
65
            _transferFrom(_ids[i], _from, _to, _values[i], msg.sender);
8✔
66
        }
67
    }
68

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

334
        // Perform the hash which determines the address of a create2 deployment.
335
        bytes32 addressBytes = keccak256(
106✔
336
            abi.encodePacked(
337
                bytes1(0xff),
338
                _linkerFactory,
339
                salt,
340
                _linkerCodeHash
341
            )
342
        );
343
        return address(uint160(uint256(addressBytes)));
106✔
344
    }
345
}
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