ETH Price: $3,103.35 (+0.71%)
Gas: 3 Gwei

Contract Diff Checker

Contract Name:
Vyper_contract

Contract Source Code:

File 1 of 1 : Vyper_contract

# @version ^0.3.6


# Interfaces

interface ILoansPeripheral:
    def lendingPoolPeripheralContract() -> address: view

interface ILendingPoolPeripheral:
    def erc20TokenContract() -> address: view


# Structs

struct Collateral:
    contractAddress: address
    tokenId: uint256
    amount: uint256

struct Loan:
    id: uint256
    amount: uint256
    interest: uint256 # parts per 10000, e.g. 2.5% is represented by 250 parts per 10000
    maturity: uint256
    startTime: uint256
    collaterals: DynArray[Collateral, 100]
    paidPrincipal: uint256
    paidInterestAmount: uint256
    started: bool
    invalidated: bool
    paid: bool
    defaulted: bool
    canceled: bool

struct TopStats:
    highestSingleCollateralLoan: Loan
    highestCollateralBundleLoan: Loan
    highestRepayment: Loan
    highestDefaultedLoan: Loan


# Events

event OwnerProposed:
    ownerIndexed: indexed(address)
    proposedOwnerIndexed: indexed(address)
    owner: address
    proposedOwner: address
    erc20TokenContract: address

event OwnershipTransferred:
    ownerIndexed: indexed(address)
    proposedOwnerIndexed: indexed(address)
    owner: address
    proposedOwner: address
    erc20TokenContract: address

event LoansPeripheralAddressSet:
    erc20TokenContractIndexed: indexed(address)
    currentValue: address
    newValue: address
    erc20TokenContract: address


# Global variables

owner: public(address)
proposedOwner: public(address)

loansPeripheral: public(address)

loans: HashMap[address, DynArray[Loan, 2**16]]
borrowedAmount: public(HashMap[address, uint256])
ongoingLoans: public(HashMap[address, uint256])

# key: bytes32 == _abi_encoded(token_address, token_id) -> map(borrower_address, loan_id)
collateralsInLoans: public(HashMap[bytes32, HashMap[address, uint256]]) # given a collateral and a borrower, what is the loan id
collateralsInLoansUsed: public(HashMap[bytes32, HashMap[address, HashMap[uint256, bool]]]) # given a collateral, a borrower and a loan id, is the collateral still used in that loan id
collateralKeys: DynArray[bytes32, 2**20] # array of collaterals expressed by their keys
collateralsUsed: public(HashMap[bytes32, bool]) # given a collateral, is it being used in a loan
collateralsData: public(HashMap[bytes32, Collateral]) # given a collateral key, what is its data
collateralsIdsByAddress: HashMap[address, DynArray[uint256, 2**20]] # given a collateral address, what are the token ids that were already in a loan

collectionsBorrowedAmount: public(HashMap[address, uint256])

# Stats
topStats: TopStats


##### INTERNAL METHODS #####

@view
@internal
def _isLoanCreated(_borrower: address, _loanId: uint256) -> bool:
    return _loanId < len(self.loans[_borrower])


@view
@internal
def _isLoanStarted(_borrower: address, _loanId: uint256) -> bool:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].started
    return False


@view
@internal
def _isLoanInvalidated(_borrower: address, _loanId: uint256) -> bool:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].invalidated
    return False


@pure
@internal
def _computeCollateralKey(_collateralAddress: address, _collateralId: uint256) -> bytes32:
  return keccak256(_abi_encode(_collateralAddress, convert(_collateralId, bytes32)))


@internal
def _addCollateralToLoan(_borrower: address, _collateral: Collateral, _loanId: uint256):
  key: bytes32 = self._computeCollateralKey(_collateral.contractAddress, _collateral.tokenId)
  self.collateralsInLoans[key][_borrower] = _loanId
  self.collateralsInLoansUsed[key][_borrower][_loanId] = True


@internal
def _removeCollateralFromLoan(_borrower: address, _collateral: Collateral, _loanId: uint256):
  key: bytes32 = self._computeCollateralKey(_collateral.contractAddress, _collateral.tokenId)
  self.collateralsInLoansUsed[key][_borrower][_loanId] = False


@internal
def _updateCollaterals(_collateral: Collateral, _toRemove: bool):
  key: bytes32 = self._computeCollateralKey(_collateral.contractAddress, _collateral.tokenId)

  if key not in self.collateralKeys and not _toRemove:
    self.collateralKeys.append(key)
    self.collateralsUsed[key] = True
    self.collateralsData[key] = _collateral
  elif key in self.collateralKeys:
    self.collateralsUsed[key] = not _toRemove

  if _collateral.tokenId not in self.collateralsIdsByAddress[_collateral.contractAddress] and not _toRemove:
    self.collateralsIdsByAddress[_collateral.contractAddress].append(_collateral.tokenId)


@internal
def _addLoan(_borrower: address, _loan: Loan) -> bool:
    if _loan.id == len(self.loans[_borrower]):
        self.loans[_borrower].append(_loan)
        self.ongoingLoans[_borrower] += 1
        return True
    return False


##### EXTERNAL METHODS #####

@external
def __init__():
    self.owner = msg.sender


@external
def proposeOwner(_address: address):
    assert msg.sender == self.owner, "msg.sender is not the owner"
    assert _address != empty(address), "_address it the zero address"
    assert self.owner != _address, "proposed owner addr is the owner"
    assert self.proposedOwner != _address, "proposed owner addr is the same"

    self.proposedOwner = _address

    log OwnerProposed(
        self.owner,
        _address,
        self.owner,
        _address,
        ILendingPoolPeripheral(
            ILoansPeripheral(self.loansPeripheral).lendingPoolPeripheralContract()
        ).erc20TokenContract()
    )


@external
def claimOwnership():
    assert msg.sender == self.proposedOwner, "msg.sender is not the proposed"

    log OwnershipTransferred(
        self.owner,
        self.proposedOwner,
        self.owner,
        self.proposedOwner,
        ILendingPoolPeripheral(
            ILoansPeripheral(self.loansPeripheral).lendingPoolPeripheralContract()
        ).erc20TokenContract()
    )

    self.owner = self.proposedOwner
    self.proposedOwner = empty(address)


@external
def setLoansPeripheral(_address: address):
    assert msg.sender == self.owner, "msg.sender is not the owner"
    assert _address != empty(address), "_address is the zero address"
    assert _address != self.loansPeripheral, "new loans addr is the same"

    log LoansPeripheralAddressSet(
        ILendingPoolPeripheral(
            ILoansPeripheral(_address).lendingPoolPeripheralContract()
        ).erc20TokenContract(),
        self.loansPeripheral,
        _address,
        ILendingPoolPeripheral(
            ILoansPeripheral(_address).lendingPoolPeripheralContract()
        ).erc20TokenContract()
    )

    self.loansPeripheral = _address


@view
@external
def isLoanCreated(_borrower: address, _loanId: uint256) -> bool:
    return self._isLoanCreated(_borrower, _loanId)


@view
@external
def isLoanStarted(_borrower: address, _loanId: uint256) -> bool:
    return self._isLoanStarted(_borrower, _loanId)


@view
@external
def getLoanAmount(_borrower: address, _loanId: uint256) -> uint256:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].amount
    return 0


@view
@external
def getLoanMaturity(_borrower: address, _loanId: uint256) -> uint256:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].maturity
    return 0


@view
@external
def getLoanInterest(_borrower: address, _loanId: uint256) -> uint256:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].interest
    return 0


@view
@external
def getLoanCollaterals(_borrower: address, _loanId: uint256) -> DynArray[Collateral, 100]:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].collaterals
    return empty(DynArray[Collateral, 100])


@view
@external
def getLoanStartTime(_borrower: address, _loanId: uint256) -> uint256:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].startTime
    return max_value(uint256)


@view
@external
def getLoanPaidPrincipal(_borrower: address, _loanId: uint256) -> uint256:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].paidPrincipal
    return 0


@view
@external
def getLoanPaidInterestAmount(_borrower: address, _loanId: uint256) -> uint256:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].paidInterestAmount
    return 0


@view
@external
def getLoanStarted(_borrower: address, _loanId: uint256) -> bool:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].started
    return False


@view
@external
def getLoanInvalidated(_borrower: address, _loanId: uint256) -> bool:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].invalidated
    return False


@view
@external
def getLoanPaid(_borrower: address, _loanId: uint256) -> bool:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].paid
    return False


@view
@external
def getLoanDefaulted(_borrower: address, _loanId: uint256) -> bool:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].defaulted
    return False


@view
@external
def getLoanCanceled(_borrower: address, _loanId: uint256) -> bool:
    if _loanId < len(self.loans[_borrower]):
        return self.loans[_borrower][_loanId].canceled
    return False


@view
@external
def getPendingLoan(_borrower: address, _loanId: uint256) -> Loan:
  if self._isLoanCreated(_borrower, _loanId) and not self._isLoanStarted(_borrower, _loanId) and not self._isLoanInvalidated(_borrower, _loanId):
    return self.loans[_borrower][_loanId]
  return empty(Loan)


@view
@external
def getLoan(_borrower: address, _loanId: uint256) -> Loan:
  if self._isLoanStarted(_borrower, _loanId) or self._isLoanInvalidated(_borrower, _loanId):
    return self.loans[_borrower][_loanId]
  return empty(Loan)


@view
@external
def getHighestSingleCollateralLoan() -> Loan:
    return self.topStats.highestSingleCollateralLoan


@view
@external
def getHighestCollateralBundleLoan() -> Loan:
    return self.topStats.highestCollateralBundleLoan


@view
@external
def getHighestRepayment() -> Loan:
    return self.topStats.highestRepayment


@view
@external
def getHighestDefaultedLoan() -> Loan:
    return self.topStats.highestDefaultedLoan


@view
@external
def collateralKeysArray() -> DynArray[bytes32, 2**20]:
  return self.collateralKeys


@view
@external
def getCollateralsIdsByAddress(_address: address) -> DynArray[uint256, 2**20]:
  return self.collateralsIdsByAddress[_address]


@external
def addCollateralToLoan(_borrower: address, _collateral: Collateral, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"
    assert self._isLoanCreated(_borrower, _loanId), "loan not found"

    self._addCollateralToLoan(_borrower, _collateral, _loanId)


@external
def removeCollateralFromLoan(_borrower: address, _collateral: Collateral, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"
    assert self._isLoanCreated(_borrower, _loanId), "loan not found"
    
    self._removeCollateralFromLoan(_borrower, _collateral, _loanId)


@external
def updateCollaterals(_collateral: Collateral, _toRemove: bool):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    self._updateCollaterals(_collateral, _toRemove)


@external
def addLoan(
    _borrower: address,
    _amount: uint256,
    _interest: uint256,
    _maturity: uint256,
    _collaterals: DynArray[Collateral, 100]
) -> uint256:
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    newLoan: Loan = Loan(
        {
            id: len(self.loans[_borrower]),
            amount: _amount,
            interest: _interest,
            maturity: _maturity,
            startTime: 0,
            collaterals: _collaterals,
            paidPrincipal: 0,
            paidInterestAmount: 0,
            started: False,
            invalidated: False,
            paid: False,
            defaulted: False,
            canceled: False,
        }
    )

    result: bool = self._addLoan(_borrower, newLoan)
    if not result:
        raise "adding loan for borrower failed"

    return newLoan.id


@external
def updateLoanStarted(_borrower: address, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    self.loans[_borrower][_loanId].startTime = block.timestamp
    self.loans[_borrower][_loanId].started = True

    self.borrowedAmount[_borrower] += self.loans[_borrower][_loanId].amount

    for collateral in self.loans[_borrower][_loanId].collaterals:
        self.collectionsBorrowedAmount[collateral.contractAddress] += collateral.amount


@external
def updateInvalidLoan(_borrower: address, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    self.loans[_borrower][_loanId].invalidated = True

    self.ongoingLoans[_borrower] -= 1


@external
def updateLoanPaidAmount(_borrower: address, _loanId: uint256, _paidPrincipal: uint256, _paidInterestAmount: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"
  
    self.loans[_borrower][_loanId].paidPrincipal += _paidPrincipal
    self.loans[_borrower][_loanId].paidInterestAmount += _paidInterestAmount


@external
def updatePaidLoan(_borrower: address, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    self.loans[_borrower][_loanId].paid = True

    self.borrowedAmount[_borrower] -= self.loans[_borrower][_loanId].amount

    self.ongoingLoans[_borrower] -= 1

    for collateral in self.loans[_borrower][_loanId].collaterals:
        self.collectionsBorrowedAmount[collateral.contractAddress] -= collateral.amount


@external
def updateDefaultedLoan(_borrower: address, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    self.loans[_borrower][_loanId].defaulted = True

    self.borrowedAmount[_borrower] -= self.loans[_borrower][_loanId].amount

    self.ongoingLoans[_borrower] -= 1

    for collateral in self.loans[_borrower][_loanId].collaterals:
        self.collectionsBorrowedAmount[collateral.contractAddress] -= collateral.amount


@external
def updateCanceledLoan(_borrower: address, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    self.loans[_borrower][_loanId].canceled = True

    self.ongoingLoans[_borrower] -= 1


@external
def updateHighestSingleCollateralLoan(_borrower: address, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"
  
    if len(self.loans[_borrower][_loanId].collaterals) == 1 and self.topStats.highestSingleCollateralLoan.amount < self.loans[_borrower][_loanId].amount:
        self.topStats.highestSingleCollateralLoan = self.loans[_borrower][_loanId]
  

@external
def updateHighestCollateralBundleLoan(_borrower: address, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    if len(self.loans[_borrower][_loanId].collaterals) > 1 and self.topStats.highestCollateralBundleLoan.amount < self.loans[_borrower][_loanId].amount:
        self.topStats.highestCollateralBundleLoan = self.loans[_borrower][_loanId]


@external
def updateHighestRepayment(_borrower: address, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    if self.topStats.highestRepayment.amount < self.loans[_borrower][_loanId].amount:
        self.topStats.highestRepayment = self.loans[_borrower][_loanId]


@external
def updateHighestDefaultedLoan(_borrower: address, _loanId: uint256):
    assert msg.sender == self.loansPeripheral, "msg.sender is not the loans addr"

    if self.topStats.highestDefaultedLoan.amount < self.loans[_borrower][_loanId].amount:
        self.topStats.highestDefaultedLoan = self.loans[_borrower][_loanId]

Please enter a contract address above to load the contract details and source code.

Context size (optional):