-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNftfiV3Adapter.sol
More file actions
130 lines (118 loc) · 4.96 KB
/
Copy pathNftfiV3Adapter.sol
File metadata and controls
130 lines (118 loc) · 4.96 KB
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;
import {IPledgeAdapter} from "../interfaces/IPledgeAdapter.sol";
import {EvmV1Decoder} from "../vendor/EvmV1Decoder.sol";
/**
* Reads NFTfi v3 on Ethereum mainnet.
*
* NFTfi is a real lending protocol with real loans against real NFTs, and it
* has never heard of this registry. That is the entire claim of the product,
* and this adapter is what it costs to make it true: one pure contract, no
* cooperation, no deployment on their side, nothing asked of anybody.
*
* Field names and layout come from the verified ABI of CollectionOfferLoan at
* 0xB6adEc2ACc851d30d5fB64f3137234BCDCBBad0D, and the signatures below were
* checked against real logs on chain rather than derived from documentation.
*
* Two honest notes about the mapping.
*
* NFTfi is custodial: `LoanTerms.escrow` names the contract holding the token
* for the duration of the loan. A borrower who has deposited the NFT cannot
* pledge it elsewhere, so no collision can originate here. That is caveat 6,
* and it is why the collision demonstration runs against non-custodial lenders
* instead.
*
* NFTfi has no separate settlement step: repayment returns the token in the
* same transaction, so `LoanRepaid` is a release, not a settlement, and the
* settlement signature is deliberately zero. The registry answers
* `TransitionUnsupported` for a settlement against this emitter, which is the
* truth rather than a convenient approximation.
*/
contract NftfiV3Adapter is IPledgeAdapter {
/// LoanStarted(uint32,address,address,(uint256,uint256,uint256,address,uint32,uint16,uint16,uint256,address,uint64,address,address,address,address,bool))
bytes32 public constant LOAN_STARTED_SIG =
0x4d3634f72248e203ec6eab4996f443daca55feea347f82ff609b2d0f5bbaae5a;
/// LoanRepaid(uint32,address,address,uint256,uint256,uint256,uint256,address,address)
bytes32 public constant LOAN_REPAID_SIG =
0x6ee3573bd905753c83bc1aaca3c15bfa36391db95b778bd825eb010645a7ee45;
uint8 internal constant KIND_PLEDGE = 0;
uint8 internal constant KIND_RELEASE = 2;
struct LoanTerms {
uint256 loanPrincipalAmount;
uint256 maximumRepaymentAmount;
uint256 nftCollateralId;
address loanERC20Denomination;
uint32 loanDuration;
uint16 loanInterestRateForDurationInBasisPoints;
uint16 loanAdminFeeInBasisPoints;
uint256 originationFee;
address nftCollateralWrapper;
uint64 loanStartTime;
address nftCollateralContract;
address borrower;
address lender;
address escrow;
bool isProRata;
}
error UnsupportedKind(uint8 kind);
error UnexpectedTopics(uint256 count);
function signaturesFor(uint8 kind) external pure returns (bytes32[] memory signatures) {
if (kind == KIND_PLEDGE) {
signatures = new bytes32[](1);
signatures[0] = LOAN_STARTED_SIG;
return signatures;
}
if (kind == KIND_RELEASE) {
signatures = new bytes32[](1);
signatures[0] = LOAN_REPAID_SIG;
return signatures;
}
// No settlement step: repayment returns the token in the same
// transaction, so there is nothing between started and closed.
return new bytes32[](0);
}
function translate(uint8 kind, EvmV1Decoder.LogEntry calldata log)
external
pure
returns (
address collateralToken,
uint256 tokenId,
address borrower,
uint256 amount,
bytes32 instanceId
)
{
if (log.topics.length != 4) revert UnexpectedTopics(log.topics.length);
// loanId leads the topics in both events, so one lien keeps one identity
// from the loan being taken to the loan being repaid.
instanceId = log.topics[1];
if (kind == KIND_PLEDGE) {
LoanTerms memory terms = abi.decode(log.data, (LoanTerms));
return (
terms.nftCollateralContract,
terms.nftCollateralId,
terms.borrower,
terms.loanPrincipalAmount,
instanceId
);
}
if (kind == KIND_RELEASE) {
/**
* The borrower in a repayment log is whoever held the obligation at
* the end, which NFTfi lets change hands during the loan. The
* registry binds a release to the emitter and the loan id, never to
* the borrower, so a transferred obligation still closes its lien.
*/
(uint256 principal, uint256 collateralId,,, address collateral,) =
abi.decode(log.data, (uint256, uint256, uint256, uint256, address, address));
return (
collateral,
collateralId,
address(uint160(uint256(log.topics[2]))),
principal,
instanceId
);
}
revert UnsupportedKind(kind);
}
}