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

delvtech / hyperdrive / 8716520331

17 Apr 2024 04:36AM UTC coverage: 92.76% (-0.5%) from 93.238%
8716520331

push

github

web-flow
external coverage to 100% and coverage config tweaks (#982)

* add external transferFrom test

* prettier

* add regular transferFrom test

* remove pinned foundry version

* remove pinned foundry version

* unpin rest of foundry

* turn off cache for coverage

* turn off optimizer on code coverage

* add coverage script and additional test

* add code coverage script

1781 of 1920 relevant lines covered (92.76%)

386752.99 hits per line

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

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

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

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

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

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

56
        // Check for inconsistent length.
57
        if (ids.length != values.length) {
8✔
58
            revert IHyperdrive.BatchInputLengthMismatch();
4✔
59
        }
60

61
        // Call internal transfer for each asset.
62
        for (uint256 i = 0; i < ids.length; ) {
9✔
63
            _transferFrom(ids[i], from, to, values[i], msg.sender);
8✔
64
            unchecked {
65
                ++i;
6✔
66
            }
67
        }
68
    }
69

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

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

107
        // Reaching this point implies the transfer is authorized so we remove
108
        // from the source and add to the destination.
109
        _balanceOf[tokenID][from] -= amount;
414✔
110
        _balanceOf[tokenID][to] += amount;
326✔
111
        emit TransferSingle(caller, from, to, tokenID, amount);
326✔
112
    }
113

114
    /// @notice Sets the approval for a sub-token.
115
    /// @param tokenID The asset to approve the use of.
116
    /// @param operator The address who will be able to use the tokens.
117
    /// @param amount The max tokens the approved person can use, setting to
118
    ///               uint256.max will cause the value to never decrement
119
    ///               [saving gas on transfer].
120
    /// @param caller The eth address which initiated the approval call.
121
    function _setApproval(
122
        uint256 tokenID,
123
        address operator,
124
        uint256 amount,
125
        address caller
126
    ) internal {
127
        _perTokenApprovals[tokenID][caller][operator] = amount;
98✔
128

129
        // Emit an event to track approval.
130
        emit Approval(caller, operator, amount);
98✔
131
    }
132

133
    /// @notice Minting function to create tokens.
134
    /// @param tokenID The asset type to create.
135
    /// @param to The address whose balance to increase.
136
    /// @param amount The number of tokens to create.
137
    /// @dev Must be used from inheriting contracts.
138
    function _mint(
139
        uint256 tokenID,
140
        address to,
141
        uint256 amount
142
    ) internal virtual {
143
        _balanceOf[tokenID][to] += amount;
123,072✔
144
        _totalSupply[tokenID] += amount;
123,072✔
145

146
        // Emit an event to track minting.
147
        emit TransferSingle(msg.sender, address(0), to, tokenID, amount);
123,072✔
148
    }
149

150
    /// @notice Burning function to remove tokens.
151
    /// @param tokenID The asset type to remove.
152
    /// @param from The address whose balance to decrease.
153
    /// @param amount The number of tokens to remove.
154
    /// @dev Must be used from inheriting contracts.
155
    function _burn(uint256 tokenID, address from, uint256 amount) internal {
156
        // Check to see if the balance is sufficient. If it isn't, throw an
157
        // insufficient balance error.
158
        if (_balanceOf[tokenID][from] < amount) {
62,132✔
159
            revert IHyperdrive.InsufficientBalance();
6✔
160
        }
161

162
        // Decrement from the source and supply.
163
        unchecked {
164
            _balanceOf[tokenID][from] -= amount;
62,126✔
165
        }
166
        _totalSupply[tokenID] -= amount;
62,126✔
167

168
        // Emit an event to track burning.
169
        emit TransferSingle(msg.sender, from, address(0), tokenID, amount);
62,126✔
170
    }
171

172
    /// @dev Allows a caller who is not the owner of an account to execute the
173
    ///      functionality of 'approve' for all assets with the owners signature.
174
    /// @param domainSeparator The EIP712 domain separator for this contract.
175
    /// @param permitTypehash The EIP712 typehash for the permit data.
176
    /// @param owner The owner of the account which is having the new approval set.
177
    /// @param spender The address which will be allowed to spend owner's tokens.
178
    /// @param _approved A boolean of the approval status to set to.
179
    /// @param deadline The timestamp which the signature must be submitted by
180
    ///        to be valid.
181
    /// @param v Extra ECDSA data which allows public key recovery from
182
    ///        signature assumed to be 27 or 28.
183
    /// @param r The r component of the ECDSA signature.
184
    /// @param s The s component of the ECDSA signature.
185
    /// @dev The signature for this function follows EIP 712 standard and should
186
    ///      be generated with the eth_signTypedData JSON RPC call instead of
187
    ///      the eth_sign JSON RPC call. If using out of date parity signing
188
    ///      libraries the v component may need to be adjusted. Also it is very
189
    ///      rare but possible for v to be other values, those values are not
190
    ///      supported.
191
    function _permitForAll(
192
        bytes32 domainSeparator,
193
        bytes32 permitTypehash,
194
        address owner,
195
        address spender,
196
        bool _approved,
197
        uint256 deadline,
198
        uint8 v,
199
        bytes32 r,
200
        bytes32 s
201
    ) internal {
202
        // Require that the signature is not expired.
203
        if (block.timestamp > deadline) {
14✔
204
            revert IHyperdrive.ExpiredDeadline();
2✔
205
        }
206

207
        // Require that the owner is not zero.
208
        if (owner == address(0)) {
18✔
209
            revert IHyperdrive.RestrictedZeroAddress();
×
210
        }
211

212
        // Check that the signature is valid and recovers to the owner.
213
        bytes32 structHash = keccak256(
18✔
214
            abi.encodePacked(
215
                "\x19\x01",
216
                domainSeparator,
217
                keccak256(
218
                    abi.encode(
219
                        permitTypehash,
220
                        owner,
221
                        spender,
222
                        _approved,
223
                        _nonces[owner],
224
                        deadline
225
                    )
226
                )
227
            )
228
        );
229
        address signer = ecrecover(structHash, v, r, s);
18✔
230
        if (signer != owner) {
12✔
231
            revert IHyperdrive.InvalidSignature();
4✔
232
        }
233

234
        // Increment the signature nonce.
235
        unchecked {
236
            ++_nonces[owner];
8✔
237
        }
238

239
        // Set the state.
240
        _isApprovedForAll[owner][spender] = _approved;
8✔
241

242
        // Emit an event to track approval.
243
        emit ApprovalForAll(owner, spender, _approved);
8✔
244
    }
245

246
    /// @notice Derive the ERC20 forwarder address for a provided `tokenId`.
247
    /// @param tokenId Token Id of the token whose forwarder contract address
248
    ///        need to derived.
249
    /// @return Address of the ERC20 forwarder contract.
250
    function _deriveForwarderAddress(
251
        uint256 tokenId
252
    ) internal view returns (address) {
253
        // Get the salt which is used by the deploying contract.
254
        bytes32 salt = keccak256(abi.encode(address(this), tokenId));
315✔
255

256
        // Perform the hash which determines the address of a create2 deployment.
257
        bytes32 addressBytes = keccak256(
315✔
258
            abi.encodePacked(
259
                bytes1(0xff),
260
                _linkerFactory,
261
                salt,
262
                _linkerCodeHash
263
            )
264
        );
265
        return address(uint160(uint256(addressBytes)));
315✔
266
    }
267
}
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