| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219 |  
 
 
 
 
 
 
 
 
 
 
 
546×
546×
546×
545×
545×
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1×
1×
 
 
 
 
1×
1×
 
 
 
 
2×
2×
 
 
 
 
1×
1×
 
 
 
 
 
42×
41×
 
40×
 
 
 
 
5×
5×
 
5×
 
 
 
 
 
197×
197×
 
197×
 
 
 
 
16×
16×
 
16×
 
 
 
 
 
 
 
191×
191×
 
191×
191×
 
191×
 
191×
1×
 
190×
 
 
191×
 
188×
 
187×
 
186×
167×
 
 
 
 
 
 
36×
36×
 
36×
36×
 
36×
 
36×
33×
 
 
36×
36×
33×
 
33×
 
32×
 
32×
 
 
 
 
 
 
 
 
9×
9×
 
9×
 
5×
 
5×
 
5×
 
5×
 
 
 
 
 
 
17×
17×
 
17×
 
17×
 
 
 
 
 
 
 
 
7×
 
 
 
 
 
 
 
28×
28×
 
28×
28×
 
25×
25×
25×
 
25×
 
 
25×
 
24×
13×
3×
3×
 
 
14×
 
13×
10×
 
10×
 
 
  | // SPDX-License-Identifier: GPL-2.0-or-later
 
pragma solidity ^0.8.0;
 
import "../BaseLogic.sol";
 
 
/// @notice Tokenised representation of debts
contract DToken is BaseLogic {
    constructor(bytes32 moduleGitCommit_) BaseLogic(MODULEID__DTOKEN, moduleGitCommit_) {}
 
    function CALLER() private view returns (address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) {
        (msgSender, proxyAddr) = unpackTrailingParams();
        address eTokenAddress = dTokenLookup[proxyAddr];
        require(eTokenAddress != address(0), "e/unrecognized-dtoken-caller");
        assetStorage = eTokenLookup[eTokenAddress];
        underlying = assetStorage.underlying;
    }
 
 
    // Events
 
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
 
 
 
    // External methods
 
    /// @notice Debt token name, ie "Euler Debt: DAI"
    function name() external view returns (string memory) {
        (address underlying,,,) = CALLER();
        return string(abi.encodePacked("Euler Debt: ", IERC20(underlying).name()));
    }
 
    /// @notice Debt token symbol, ie "dDAI"
    function symbol() external view returns (string memory) {
        (address underlying,,,) = CALLER();
        return string(abi.encodePacked("d", IERC20(underlying).symbol()));
    }
 
    /// @notice Decimals of underlying
    function decimals() external view returns (uint8) {
        (,AssetStorage storage assetStorage,,) = CALLER();
        return assetStorage.underlyingDecimals;
    }
 
    /// @notice Address of underlying asset
    function underlyingAsset() external view returns (address) {
        (address underlying,,,) = CALLER();
        return underlying;
    }
 
 
    /// @notice Sum of all outstanding debts, in underlying units (increases as interest is accrued)
    function totalSupply() external view returns (uint) {
        (address underlying, AssetStorage storage assetStorage,,) = CALLER();
        AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
 
        return assetCache.totalBorrows / INTERNAL_DEBT_PRECISION / assetCache.underlyingDecimalsScaler;
    }
 
    /// @notice Sum of all outstanding debts, in underlying units normalized to 27 decimals (increases as interest is accrued)
    function totalSupplyExact() external view returns (uint) {
        (address underlying, AssetStorage storage assetStorage,,) = CALLER();
        AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
 
        return assetCache.totalBorrows;
    }
 
 
    /// @notice Debt owed by a particular account, in underlying units
    function balanceOf(address account) external view returns (uint) {
        (address underlying, AssetStorage storage assetStorage,,) = CALLER();
        AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
 
        return getCurrentOwed(assetStorage, assetCache, account) / assetCache.underlyingDecimalsScaler;
    }
 
    /// @notice Debt owed by a particular account, in underlying units normalized to 27 decimals
    function balanceOfExact(address account) external view returns (uint) {
        (address underlying, AssetStorage storage assetStorage,,) = CALLER();
        AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
 
        return getCurrentOwedExact(assetStorage, assetCache, account, assetStorage.users[account].owed);
    }
 
 
    /// @notice Transfer underlying tokens from the Euler pool to the sender, and increase sender's dTokens
    /// @param subAccountId 0 for primary, 1-255 for a sub-account
    /// @param amount In underlying units (use max uint256 for all available tokens)
    function borrow(uint subAccountId, uint amount) external nonReentrant {
        (address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
        address account = getSubAccount(msgSender, subAccountId);
 
        updateAverageLiquidity(account);
        emit RequestBorrow(account, amount);
 
        AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
 
        if (amount == type(uint).max) {
            amount = assetCache.poolSize;
        } else {
            amount = decodeExternalAmount(assetCache, amount);
        }
 
        require(amount <= assetCache.poolSize, "e/insufficient-tokens-available");
 
        pushTokens(assetCache, msgSender, amount);
 
        increaseBorrow(assetStorage, assetCache, proxyAddr, account, amount);
 
        checkLiquidity(account);
        logAssetStatus(assetCache);
    }
 
    /// @notice Transfer underlying tokens from the sender to the Euler pool, and decrease sender's dTokens
    /// @param subAccountId 0 for primary, 1-255 for a sub-account
    /// @param amount In underlying units (use max uint256 for full debt owed)
    function repay(uint subAccountId, uint amount) external nonReentrant {
        (address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
        address account = getSubAccount(msgSender, subAccountId);
 
        updateAverageLiquidity(account);
        emit RequestRepay(account, amount);
 
        AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
 
        if (amount != type(uint).max) {
            amount = decodeExternalAmount(assetCache, amount);
        }
 
        uint owed = getCurrentOwed(assetStorage, assetCache, account);
        if (owed == 0) return;
        if (amount > owed) amount = owed;
 
        amount = pullTokens(assetCache, msgSender, amount);
 
        decreaseBorrow(assetStorage, assetCache, proxyAddr, account, amount);
 
        logAssetStatus(assetCache);
    }
 
 
    /// @notice Allow spender to send an amount of dTokens to a particular sub-account
    /// @param subAccountId 0 for primary, 1-255 for a sub-account
    /// @param spender Trusted address
    /// @param amount In underlying units (use max uint256 for "infinite" allowance)
    function approveDebt(uint subAccountId, address spender, uint amount) public nonReentrant returns (bool) {
        (address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
        address account = getSubAccount(msgSender, subAccountId);
 
        require(!isSubAccountOf(spender, account), "e/self-approval");
 
        AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
 
        assetStorage.dTokenAllowance[account][spender] = amount == type(uint).max ? type(uint).max : decodeExternalAmount(assetCache, amount);
 
        emitViaProxy_Approval(proxyAddr, account, spender, amount);
 
        return true;
    }
 
    /// @notice Retrieve the current debt allowance
    /// @param holder Xor with the desired sub-account ID (if applicable)
    /// @param spender Trusted address
    function debtAllowance(address holder, address spender) external view returns (uint) {
        (address underlying, AssetStorage storage assetStorage,,) = CALLER();
        AssetCache memory assetCache = loadAssetCacheRO(underlying, assetStorage);
 
        uint allowance = assetStorage.dTokenAllowance[holder][spender];
 
        return allowance == type(uint).max ? type(uint).max : allowance / assetCache.underlyingDecimalsScaler;
    }
 
 
 
    /// @notice Transfer dTokens to another address (from sub-account 0)
    /// @param to Xor with the desired sub-account ID (if applicable)
    /// @param amount In underlying units. Use max uint256 for full balance.
    function transfer(address to, uint amount) external reentrantOK returns (bool) {
        return transferFrom(address(0), to, amount);
    }
 
    /// @notice Transfer dTokens from one address to another
    /// @param from Xor with the desired sub-account ID (if applicable)
    /// @param to This address must've approved the from address, or be a sub-account of msg.sender
    /// @param amount In underlying units. Use max uint256 for full balance.
    function transferFrom(address from, address to, uint amount) public nonReentrant returns (bool) {
        (address underlying, AssetStorage storage assetStorage, address proxyAddr, address msgSender) = CALLER();
        AssetCache memory assetCache = loadAssetCache(underlying, assetStorage);
 
        if (from == address(0)) from = msgSender;
        require(from != to, "e/self-transfer");
 
        updateAverageLiquidity(from);
        updateAverageLiquidity(to);
        emit RequestTransferDToken(from, to, amount);
 
        if (amount == type(uint).max) amount = getCurrentOwed(assetStorage, assetCache, from);
        else amount = decodeExternalAmount(assetCache, amount);
 
        if (amount == 0) return true;
 
        if (!isSubAccountOf(msgSender, to) && assetStorage.dTokenAllowance[to][msgSender] != type(uint).max) {
            require(assetStorage.dTokenAllowance[to][msgSender] >= amount, "e/insufficient-debt-allowance");
            unchecked { assetStorage.dTokenAllowance[to][msgSender] -= amount; }
            emitViaProxy_Approval(proxyAddr, to, msgSender, assetStorage.dTokenAllowance[to][msgSender] / assetCache.underlyingDecimalsScaler);
        }
 
        transferBorrow(assetStorage, assetCache, proxyAddr, from, to, amount);
 
        checkLiquidity(to);
        logAssetStatus(assetCache);
 
        return true;
    }
}
  |