Rate model/integration (#2)
* evm: fix interest calculations and tests * Add collateral change messages Co-authored-by: Drew <dsterioti@users.noreply.github.com>
This commit is contained in:
parent
0fd688c7ff
commit
f67eee8b5e
|
@ -54,8 +54,12 @@ contract CrossChainBorrowLend is
|
|||
|
||||
// Price index of 1 with the current precision is 1e18
|
||||
// since this is the precision of our value.
|
||||
state.interestAccrualIndexPrecision = 1e18;
|
||||
state.interestAccrualIndex = state.interestAccrualIndexPrecision;
|
||||
uint256 precision = 1e18;
|
||||
state.interestAccrualIndexPrecision = precision;
|
||||
state.interestAccrualIndex.source.deposited = precision;
|
||||
state.interestAccrualIndex.source.borrowed = precision;
|
||||
state.interestAccrualIndex.target.deposited = precision;
|
||||
state.interestAccrualIndex.target.borrowed = precision;
|
||||
|
||||
// pyth oracle address and asset IDs
|
||||
state.mockPythAddress = mockPythAddress_;
|
||||
|
@ -66,19 +70,19 @@ contract CrossChainBorrowLend is
|
|||
state.repayGracePeriod = repayGracePeriod_;
|
||||
}
|
||||
|
||||
function addCollateral(uint256 amount) public nonReentrant {
|
||||
function addCollateral(uint256 amount) public nonReentrant returns (uint64 sequence) {
|
||||
require(amount > 0, "nothing to deposit");
|
||||
|
||||
// update current price index
|
||||
updateInterestAccrualIndex();
|
||||
updateSourceInterestAccrualIndex();
|
||||
|
||||
// update state for supplier
|
||||
uint256 normalizedAmount = normalizeAmount(
|
||||
amount,
|
||||
collateralInterestAccrualIndex()
|
||||
sourceCollateralInterestAccrualIndex()
|
||||
);
|
||||
state.accountAssets[_msgSender()].sourceDeposited += normalizedAmount;
|
||||
state.totalAssets.deposited += normalizedAmount;
|
||||
state.accountAssets[_msgSender()].source.deposited += normalizedAmount;
|
||||
state.totalAssets.source.deposited += normalizedAmount;
|
||||
|
||||
SafeERC20.safeTransferFrom(
|
||||
collateralToken(),
|
||||
|
@ -86,13 +90,31 @@ contract CrossChainBorrowLend is
|
|||
address(this),
|
||||
amount
|
||||
);
|
||||
|
||||
// construct wormhole message
|
||||
MessageHeader memory header = MessageHeader({
|
||||
payloadID: uint8(5),
|
||||
sender: _msgSender(),
|
||||
collateralAddress: state.collateralAssetAddress,
|
||||
borrowAddress: state.borrowingAssetAddress
|
||||
});
|
||||
|
||||
sequence = sendWormholeMessage(
|
||||
encodeDepositChangeMessage(
|
||||
DepositChangeMessage({
|
||||
header: header,
|
||||
depositType: DepositType.Add,
|
||||
amount: normalizeAmount(amount, sourceCollateralInterestAccrualIndex())
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function removeCollateral(uint256 amount) public nonReentrant {
|
||||
function removeCollateral(uint256 amount) public nonReentrant returns (uint64 sequence) {
|
||||
require(amount > 0, "nothing to withdraw");
|
||||
|
||||
// update current price index
|
||||
updateInterestAccrualIndex();
|
||||
updateSourceInterestAccrualIndex();
|
||||
|
||||
// Check if user has enough to withdraw from the contract
|
||||
require(
|
||||
|
@ -103,34 +125,47 @@ contract CrossChainBorrowLend is
|
|||
// update state for supplier
|
||||
uint256 normalizedAmount = normalizeAmount(
|
||||
amount,
|
||||
collateralInterestAccrualIndex()
|
||||
sourceCollateralInterestAccrualIndex()
|
||||
);
|
||||
state.accountAssets[_msgSender()].sourceDeposited -= normalizedAmount;
|
||||
state.totalAssets.deposited -= normalizedAmount;
|
||||
state.accountAssets[_msgSender()].source.deposited -= normalizedAmount;
|
||||
state.totalAssets.source.deposited -= normalizedAmount;
|
||||
|
||||
// transfer the tokens to the caller
|
||||
SafeERC20.safeTransfer(collateralToken(), _msgSender(), amount);
|
||||
|
||||
// construct wormhole message
|
||||
MessageHeader memory header = MessageHeader({
|
||||
payloadID: uint8(5),
|
||||
sender: _msgSender(),
|
||||
collateralAddress: state.collateralAssetAddress,
|
||||
borrowAddress: state.borrowingAssetAddress
|
||||
});
|
||||
|
||||
sequence = sendWormholeMessage(
|
||||
encodeDepositChangeMessage(
|
||||
DepositChangeMessage({
|
||||
header: header,
|
||||
depositType: DepositType.Remove,
|
||||
amount: normalizedAmount
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function removeCollateralInFull() public nonReentrant {
|
||||
function removeCollateralInFull() public nonReentrant returns (uint64 sequence) {
|
||||
// fetch the account information for the caller
|
||||
NormalizedAmounts memory normalizedAmounts = state.accountAssets[
|
||||
_msgSender()
|
||||
];
|
||||
SourceTargetUints memory account = state.accountAssets[_msgSender()];
|
||||
|
||||
// make sure the account has closed all borrowed positions
|
||||
require(
|
||||
normalizedAmounts.targetBorrowed == 0,
|
||||
"account has outstanding loans"
|
||||
);
|
||||
require(account.target.borrowed == 0, "account has outstanding loans");
|
||||
|
||||
// update current price index
|
||||
updateInterestAccrualIndex();
|
||||
updateSourceInterestAccrualIndex();
|
||||
|
||||
// update state for supplier
|
||||
uint256 normalizedAmount = normalizedAmounts.sourceDeposited;
|
||||
state.accountAssets[_msgSender()].sourceDeposited = 0;
|
||||
state.totalAssets.deposited -= normalizedAmount;
|
||||
uint256 normalizedAmount = account.source.deposited;
|
||||
state.accountAssets[_msgSender()].source.deposited = 0;
|
||||
state.totalAssets.source.deposited -= normalizedAmount;
|
||||
|
||||
// transfer the tokens to the caller
|
||||
SafeERC20.safeTransfer(
|
||||
|
@ -138,32 +173,124 @@ contract CrossChainBorrowLend is
|
|||
_msgSender(),
|
||||
denormalizeAmount(
|
||||
normalizedAmount,
|
||||
collateralInterestAccrualIndex()
|
||||
sourceCollateralInterestAccrualIndex()
|
||||
)
|
||||
);
|
||||
|
||||
// construct wormhole message
|
||||
MessageHeader memory header = MessageHeader({
|
||||
payloadID: uint8(5),
|
||||
sender: _msgSender(),
|
||||
collateralAddress: state.collateralAssetAddress,
|
||||
borrowAddress: state.borrowingAssetAddress
|
||||
});
|
||||
|
||||
sequence = sendWormholeMessage(
|
||||
encodeDepositChangeMessage(
|
||||
DepositChangeMessage({
|
||||
header: header,
|
||||
depositType: DepositType.RemoveFull,
|
||||
amount: normalizedAmount
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function computeInterestProportion(
|
||||
function completeCollateralChange(bytes memory encodedVm) public {
|
||||
// parse and verify the wormhole BorrowMessage
|
||||
(
|
||||
IWormhole.VM memory parsed,
|
||||
bool valid,
|
||||
string memory reason
|
||||
) = wormhole().parseAndVerifyVM(encodedVm);
|
||||
require(valid, reason);
|
||||
|
||||
// verify emitter
|
||||
require(verifyEmitter(parsed), "invalid emitter");
|
||||
|
||||
// completed (replay protection)
|
||||
// also serves as reentrancy protection
|
||||
require(!messageHashConsumed(parsed.hash), "message already consumed");
|
||||
consumeMessageHash(parsed.hash);
|
||||
|
||||
// decode deposit change message
|
||||
DepositChangeMessage memory params = decodeDepositChangeMessage(parsed.payload);
|
||||
address depositor = params.header.sender;
|
||||
|
||||
// correct assets?
|
||||
require(
|
||||
params.header.collateralAddress == state.borrowingAssetAddress &&
|
||||
params.header.borrowAddress == state.collateralAssetAddress,
|
||||
"invalid asset metadata"
|
||||
);
|
||||
|
||||
// update current price index
|
||||
updateTargetInterestAccrualIndex();
|
||||
|
||||
// update this contracts state to reflect the deposit change
|
||||
if (params.depositType == DepositType.Add) {
|
||||
state.totalAssets.target.deposited += params.amount;
|
||||
state.accountAssets[depositor].target.deposited += params.amount;
|
||||
} else if (params.depositType == DepositType.Remove) {
|
||||
state.totalAssets.target.deposited -= params.amount;
|
||||
state.accountAssets[depositor].target.deposited -= params.amount;
|
||||
} else if (params.depositType == DepositType.RemoveFull) {
|
||||
// fetch the deposit amount from state
|
||||
state.totalAssets.target.deposited -= state.accountAssets[depositor].target.deposited;
|
||||
state.accountAssets[depositor].target.deposited = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function computeSourceInterestFactor(
|
||||
uint256 secondsElapsed,
|
||||
uint256 intercept,
|
||||
uint256 coefficient
|
||||
) internal view returns (uint256) {
|
||||
uint256 deposited = state.totalAssets.deposited;
|
||||
return
|
||||
_computeInterestFactor(
|
||||
secondsElapsed,
|
||||
intercept,
|
||||
coefficient,
|
||||
state.totalAssets.source.deposited,
|
||||
state.totalAssets.source.borrowed
|
||||
);
|
||||
}
|
||||
|
||||
function computeTargetInterestFactor(
|
||||
uint256 secondsElapsed,
|
||||
uint256 intercept,
|
||||
uint256 coefficient
|
||||
) internal view returns (uint256) {
|
||||
return
|
||||
_computeInterestFactor(
|
||||
secondsElapsed,
|
||||
intercept,
|
||||
coefficient,
|
||||
state.totalAssets.target.deposited,
|
||||
state.totalAssets.target.borrowed
|
||||
);
|
||||
}
|
||||
|
||||
function _computeInterestFactor(
|
||||
uint256 secondsElapsed,
|
||||
uint256 intercept,
|
||||
uint256 coefficient,
|
||||
uint256 deposited,
|
||||
uint256 borrowed
|
||||
) internal pure returns (uint256) {
|
||||
if (deposited == 0) {
|
||||
return 0;
|
||||
}
|
||||
return
|
||||
(secondsElapsed *
|
||||
(intercept +
|
||||
(coefficient * state.totalAssets.borrowed) /
|
||||
deposited)) /
|
||||
(intercept + (coefficient * borrowed) / deposited)) /
|
||||
365 /
|
||||
24 /
|
||||
60 /
|
||||
60;
|
||||
}
|
||||
|
||||
function updateInterestAccrualIndex() internal {
|
||||
function updateSourceInterestAccrualIndex() internal {
|
||||
// TODO: change to block.number?
|
||||
uint256 secondsElapsed = block.timestamp -
|
||||
state.lastActivityBlockTimestamp;
|
||||
|
@ -176,25 +303,57 @@ contract CrossChainBorrowLend is
|
|||
// Should not hit, but just here in case someone
|
||||
// tries to update the interest when there is nothing
|
||||
// deposited.
|
||||
uint256 deposited = state.totalAssets.deposited;
|
||||
uint256 deposited = state.totalAssets.source.deposited;
|
||||
if (deposited == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.lastActivityBlockTimestamp = block.timestamp;
|
||||
|
||||
state.interestAccrualIndex += computeInterestProportion(
|
||||
uint256 interestFactor = computeSourceInterestFactor(
|
||||
secondsElapsed,
|
||||
state.interestRateModel.rateIntercept,
|
||||
state.interestRateModel.rateCoefficientA
|
||||
);
|
||||
state.interestAccrualIndex.source.borrowed += interestFactor;
|
||||
state.interestAccrualIndex.source.deposited +=
|
||||
(interestFactor * state.totalAssets.source.borrowed) /
|
||||
deposited;
|
||||
}
|
||||
|
||||
function updateTargetInterestAccrualIndex() internal {
|
||||
uint256 secondsElapsed = block.timestamp -
|
||||
state.lastActivityBlockTimestamp;
|
||||
|
||||
if (secondsElapsed == 0) {
|
||||
// nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
// Should not hit, but just here in case someone
|
||||
// tries to update the interest when there is nothing
|
||||
// deposited.
|
||||
uint256 deposited = state.totalAssets.target.deposited;
|
||||
if (deposited == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
state.lastActivityBlockTimestamp = block.timestamp;
|
||||
uint256 interestFactor = computeTargetInterestFactor(
|
||||
secondsElapsed,
|
||||
state.interestRateModel.rateIntercept,
|
||||
state.interestRateModel.rateCoefficientA
|
||||
);
|
||||
state.interestAccrualIndex.target.borrowed += interestFactor;
|
||||
state.interestAccrualIndex.target.deposited +=
|
||||
(interestFactor * state.totalAssets.target.borrowed) /
|
||||
deposited;
|
||||
}
|
||||
|
||||
function initiateBorrow(uint256 amount) public returns (uint64 sequence) {
|
||||
require(amount > 0, "nothing to borrow");
|
||||
|
||||
// update current price index
|
||||
updateInterestAccrualIndex();
|
||||
updateTargetInterestAccrualIndex();
|
||||
|
||||
// Check if user has enough to borrow
|
||||
require(
|
||||
|
@ -203,15 +362,15 @@ contract CrossChainBorrowLend is
|
|||
);
|
||||
|
||||
// update state for borrower
|
||||
uint256 borrowedIndex = borrowedInterestAccrualIndex();
|
||||
uint256 borrowedIndex = targetBorrowedInterestAccrualIndex();
|
||||
uint256 normalizedAmount = normalizeAmount(amount, borrowedIndex);
|
||||
state.accountAssets[_msgSender()].targetBorrowed += normalizedAmount;
|
||||
state.totalAssets.borrowed += normalizedAmount;
|
||||
state.accountAssets[_msgSender()].target.borrowed += normalizedAmount;
|
||||
state.totalAssets.target.borrowed += normalizedAmount;
|
||||
|
||||
// construct wormhole message
|
||||
MessageHeader memory header = MessageHeader({
|
||||
payloadID: uint8(1),
|
||||
borrower: _msgSender(),
|
||||
sender: _msgSender(),
|
||||
collateralAddress: state.collateralAssetAddress,
|
||||
borrowAddress: state.borrowingAssetAddress
|
||||
});
|
||||
|
@ -223,7 +382,8 @@ contract CrossChainBorrowLend is
|
|||
borrowAmount: amount,
|
||||
totalNormalizedBorrowAmount: state
|
||||
.accountAssets[_msgSender()]
|
||||
.targetBorrowed,
|
||||
.target
|
||||
.borrowed,
|
||||
interestAccrualIndex: borrowedIndex
|
||||
})
|
||||
)
|
||||
|
@ -252,23 +412,26 @@ contract CrossChainBorrowLend is
|
|||
|
||||
// decode borrow message
|
||||
BorrowMessage memory params = decodeBorrowMessage(parsed.payload);
|
||||
address borrower = params.header.sender;
|
||||
|
||||
// correct assets?
|
||||
require(verifyAssetMetaFromBorrow(params), "invalid asset metadata");
|
||||
|
||||
// update current price index
|
||||
updateSourceInterestAccrualIndex();
|
||||
|
||||
// make sure this contract has enough assets to fund the borrow
|
||||
if (
|
||||
params.borrowAmount >
|
||||
denormalizeAmount(
|
||||
normalizedLiquidity(),
|
||||
borrowedInterestAccrualIndex()
|
||||
)
|
||||
normalizeAmount(
|
||||
params.borrowAmount,
|
||||
sourceBorrowedInterestAccrualIndex()
|
||||
) > sourceLiquidity()
|
||||
) {
|
||||
// construct RevertBorrow wormhole message
|
||||
// switch the borrow and collateral addresses for the target chain
|
||||
MessageHeader memory header = MessageHeader({
|
||||
payloadID: uint8(2),
|
||||
borrower: params.header.borrower,
|
||||
sender: borrower,
|
||||
collateralAddress: state.borrowingAssetAddress,
|
||||
borrowAddress: state.collateralAssetAddress
|
||||
});
|
||||
|
@ -284,10 +447,10 @@ contract CrossChainBorrowLend is
|
|||
);
|
||||
} else {
|
||||
// save the total normalized borrow amount for repayments
|
||||
state.totalAssets.borrowed +=
|
||||
state.totalAssets.source.borrowed +=
|
||||
params.totalNormalizedBorrowAmount -
|
||||
state.accountAssets[params.header.borrower].sourceBorrowed;
|
||||
state.accountAssets[params.header.borrower].sourceBorrowed = params
|
||||
state.accountAssets[borrower].source.borrowed;
|
||||
state.accountAssets[borrower].source.borrowed = params
|
||||
.totalNormalizedBorrowAmount;
|
||||
|
||||
// params.borrowAmount == 0 means that there was a repayment
|
||||
|
@ -299,7 +462,7 @@ contract CrossChainBorrowLend is
|
|||
SafeERC20.safeTransferFrom(
|
||||
collateralToken(),
|
||||
address(this),
|
||||
params.header.borrower,
|
||||
borrower,
|
||||
params.borrowAmount
|
||||
);
|
||||
}
|
||||
|
@ -345,9 +508,10 @@ contract CrossChainBorrowLend is
|
|||
params.sourceInterestAccrualIndex
|
||||
);
|
||||
state
|
||||
.accountAssets[params.header.borrower]
|
||||
.targetBorrowed -= normalizedAmount;
|
||||
state.totalAssets.borrowed -= normalizedAmount;
|
||||
.accountAssets[params.header.sender]
|
||||
.target
|
||||
.borrowed -= normalizedAmount;
|
||||
state.totalAssets.target.borrowed -= normalizedAmount;
|
||||
}
|
||||
|
||||
function initiateRepay(uint256 amount)
|
||||
|
@ -360,28 +524,26 @@ contract CrossChainBorrowLend is
|
|||
// For EVMs, same private key will be used for borrowing-lending activity.
|
||||
// When introducing other chains (e.g. Cosmos), need to do wallet registration
|
||||
// so we can access a map of a non-EVM address based on this EVM borrower
|
||||
NormalizedAmounts memory normalizedAmounts = state.accountAssets[
|
||||
_msgSender()
|
||||
];
|
||||
SourceTargetUints memory account = state.accountAssets[_msgSender()];
|
||||
|
||||
// update the index
|
||||
updateInterestAccrualIndex();
|
||||
updateSourceInterestAccrualIndex();
|
||||
|
||||
// cache the index to save gas
|
||||
uint256 index = borrowedInterestAccrualIndex();
|
||||
uint256 borrowedIndex = sourceBorrowedInterestAccrualIndex();
|
||||
|
||||
// save the normalized amount
|
||||
uint256 normalizedAmount = normalizeAmount(amount, index);
|
||||
uint256 normalizedAmount = normalizeAmount(amount, borrowedIndex);
|
||||
|
||||
// confirm that the caller has loans to pay back
|
||||
require(
|
||||
normalizedAmount <= normalizedAmounts.sourceBorrowed,
|
||||
normalizedAmount <= account.source.borrowed,
|
||||
"loan payment too large"
|
||||
);
|
||||
|
||||
// update state on this contract
|
||||
state.accountAssets[_msgSender()].sourceBorrowed -= normalizedAmount;
|
||||
state.totalAssets.borrowed -= normalizedAmount;
|
||||
state.accountAssets[_msgSender()].source.borrowed -= normalizedAmount;
|
||||
state.totalAssets.source.borrowed -= normalizedAmount;
|
||||
|
||||
// transfer to this contract
|
||||
SafeERC20.safeTransferFrom(
|
||||
|
@ -394,7 +556,7 @@ contract CrossChainBorrowLend is
|
|||
// construct wormhole message
|
||||
MessageHeader memory header = MessageHeader({
|
||||
payloadID: uint8(3),
|
||||
borrower: _msgSender(),
|
||||
sender: _msgSender(),
|
||||
collateralAddress: state.borrowingAssetAddress,
|
||||
borrowAddress: state.collateralAssetAddress
|
||||
});
|
||||
|
@ -405,7 +567,7 @@ contract CrossChainBorrowLend is
|
|||
RepayMessage({
|
||||
header: header,
|
||||
repayAmount: amount,
|
||||
targetInterestAccrualIndex: index,
|
||||
targetInterestAccrualIndex: borrowedIndex,
|
||||
repayTimestamp: block.timestamp,
|
||||
paidInFull: 0
|
||||
})
|
||||
|
@ -421,33 +583,31 @@ contract CrossChainBorrowLend is
|
|||
// For EVMs, same private key will be used for borrowing-lending activity.
|
||||
// When introducing other chains (e.g. Cosmos), need to do wallet registration
|
||||
// so we can access a map of a non-EVM address based on this EVM borrower
|
||||
NormalizedAmounts memory normalizedAmounts = state.accountAssets[
|
||||
_msgSender()
|
||||
];
|
||||
SourceTargetUints memory account = state.accountAssets[_msgSender()];
|
||||
|
||||
// update the index
|
||||
updateInterestAccrualIndex();
|
||||
updateSourceInterestAccrualIndex();
|
||||
|
||||
// cache the index to save gas
|
||||
uint256 index = borrowedInterestAccrualIndex();
|
||||
uint256 borrowedIndex = sourceBorrowedInterestAccrualIndex();
|
||||
|
||||
// update state on the contract
|
||||
uint256 normalizedAmount = normalizedAmounts.sourceBorrowed;
|
||||
state.accountAssets[_msgSender()].sourceBorrowed = 0;
|
||||
state.totalAssets.borrowed -= normalizedAmount;
|
||||
uint256 normalizedAmount = account.source.borrowed;
|
||||
state.accountAssets[_msgSender()].source.borrowed = 0;
|
||||
state.totalAssets.source.borrowed -= normalizedAmount;
|
||||
|
||||
// transfer to this contract
|
||||
SafeERC20.safeTransferFrom(
|
||||
borrowToken(),
|
||||
_msgSender(),
|
||||
address(this),
|
||||
denormalizeAmount(normalizedAmount, index)
|
||||
denormalizeAmount(normalizedAmount, borrowedIndex)
|
||||
);
|
||||
|
||||
// construct wormhole message
|
||||
MessageHeader memory header = MessageHeader({
|
||||
payloadID: uint8(3),
|
||||
borrower: _msgSender(),
|
||||
sender: _msgSender(),
|
||||
collateralAddress: state.borrowingAssetAddress,
|
||||
borrowAddress: state.collateralAssetAddress
|
||||
});
|
||||
|
@ -457,8 +617,11 @@ contract CrossChainBorrowLend is
|
|||
encodeRepayMessage(
|
||||
RepayMessage({
|
||||
header: header,
|
||||
repayAmount: denormalizeAmount(normalizedAmount, index),
|
||||
targetInterestAccrualIndex: index,
|
||||
repayAmount: denormalizeAmount(
|
||||
normalizedAmount,
|
||||
borrowedIndex
|
||||
),
|
||||
targetInterestAccrualIndex: borrowedIndex,
|
||||
repayTimestamp: block.timestamp,
|
||||
paidInFull: 1
|
||||
})
|
||||
|
@ -486,13 +649,14 @@ contract CrossChainBorrowLend is
|
|||
consumeMessageHash(parsed.hash);
|
||||
|
||||
// update the index
|
||||
updateInterestAccrualIndex();
|
||||
updateTargetInterestAccrualIndex();
|
||||
|
||||
// cache the index to save gas
|
||||
uint256 index = borrowedInterestAccrualIndex();
|
||||
uint256 borrowedIndex = targetBorrowedInterestAccrualIndex();
|
||||
|
||||
// decode the RepayMessage
|
||||
RepayMessage memory params = decodeRepayMessage(parsed.payload);
|
||||
address borrower = params.header.sender;
|
||||
|
||||
// correct assets?
|
||||
require(verifyAssetMetaFromRepay(params), "invalid asset metadata");
|
||||
|
@ -509,17 +673,18 @@ contract CrossChainBorrowLend is
|
|||
params.repayAmount,
|
||||
params.targetInterestAccrualIndex
|
||||
);
|
||||
state.accountAssets[params.header.borrower].targetBorrowed = 0;
|
||||
state.totalAssets.borrowed -= normalizedAmount;
|
||||
state.accountAssets[borrower].target.borrowed = 0;
|
||||
state.totalAssets.target.borrowed -= normalizedAmount;
|
||||
} else {
|
||||
uint256 normalizedAmount = normalizeAmount(
|
||||
params.repayAmount,
|
||||
index
|
||||
borrowedIndex
|
||||
);
|
||||
state
|
||||
.accountAssets[params.header.borrower]
|
||||
.targetBorrowed -= normalizedAmount;
|
||||
state.totalAssets.borrowed -= normalizedAmount;
|
||||
.accountAssets[borrower]
|
||||
.target
|
||||
.borrowed -= normalizedAmount;
|
||||
state.totalAssets.target.borrowed -= normalizedAmount;
|
||||
|
||||
// Send a wormhole message again since he did not repay in full
|
||||
// (due to repaying outside of the grace period)
|
||||
|
@ -528,15 +693,16 @@ contract CrossChainBorrowLend is
|
|||
BorrowMessage({
|
||||
header: MessageHeader({
|
||||
payloadID: uint8(1),
|
||||
borrower: params.header.borrower,
|
||||
sender: borrower,
|
||||
collateralAddress: state.collateralAssetAddress,
|
||||
borrowAddress: state.borrowingAssetAddress
|
||||
}),
|
||||
borrowAmount: 0, // special value to indicate failed repay in full
|
||||
totalNormalizedBorrowAmount: state
|
||||
.accountAssets[params.header.borrower]
|
||||
.targetBorrowed,
|
||||
interestAccrualIndex: index
|
||||
.accountAssets[borrower]
|
||||
.target
|
||||
.borrowed,
|
||||
interestAccrualIndex: borrowedIndex
|
||||
})
|
||||
)
|
||||
);
|
||||
|
@ -547,10 +713,8 @@ contract CrossChainBorrowLend is
|
|||
params.repayAmount,
|
||||
params.targetInterestAccrualIndex
|
||||
);
|
||||
state
|
||||
.accountAssets[params.header.borrower]
|
||||
.targetBorrowed -= normalizedAmount;
|
||||
state.totalAssets.borrowed -= normalizedAmount;
|
||||
state.accountAssets[borrower].target.borrowed -= normalizedAmount;
|
||||
state.totalAssets.target.borrowed -= normalizedAmount;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -53,29 +53,46 @@ contract CrossChainBorrowLendGetters is Context, CrossChainBorrowLendState {
|
|||
);
|
||||
}
|
||||
|
||||
function collateralInterestAccrualIndex() public view returns (uint256) {
|
||||
uint256 deposited = state.totalAssets.deposited;
|
||||
uint256 precision = state.interestAccrualIndexPrecision;
|
||||
if (deposited == 0) {
|
||||
return precision;
|
||||
}
|
||||
return
|
||||
precision +
|
||||
(state.totalAssets.borrowed *
|
||||
(state.interestAccrualIndex - precision)) /
|
||||
deposited;
|
||||
function sourceCollateralInterestAccrualIndex()
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return state.interestAccrualIndex.source.deposited;
|
||||
}
|
||||
|
||||
function borrowedInterestAccrualIndex() public view returns (uint256) {
|
||||
return state.interestAccrualIndex;
|
||||
function targetCollateralInterestAccrualIndex()
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return state.interestAccrualIndex.target.deposited;
|
||||
}
|
||||
|
||||
function sourceBorrowedInterestAccrualIndex()
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return state.interestAccrualIndex.source.borrowed;
|
||||
}
|
||||
|
||||
function targetBorrowedInterestAccrualIndex()
|
||||
public
|
||||
view
|
||||
returns (uint256)
|
||||
{
|
||||
return state.interestAccrualIndex.target.borrowed;
|
||||
}
|
||||
|
||||
function mockPyth() internal view returns (IMockPyth) {
|
||||
return IMockPyth(state.mockPythAddress);
|
||||
}
|
||||
|
||||
function normalizedLiquidity() internal view returns (uint256) {
|
||||
return state.totalAssets.deposited - state.totalAssets.borrowed;
|
||||
function sourceLiquidity() internal view returns (uint256) {
|
||||
return
|
||||
state.totalAssets.source.deposited -
|
||||
state.totalAssets.source.borrowed;
|
||||
}
|
||||
|
||||
function denormalizeAmount(
|
||||
|
@ -103,7 +120,7 @@ contract CrossChainBorrowLendGetters is Context, CrossChainBorrowLendState {
|
|||
function normalizedAmounts()
|
||||
public
|
||||
view
|
||||
returns (NormalizedTotalAmounts memory)
|
||||
returns (SourceTargetUints memory)
|
||||
{
|
||||
return state.totalAssets;
|
||||
}
|
||||
|
@ -116,16 +133,16 @@ contract CrossChainBorrowLendGetters is Context, CrossChainBorrowLendState {
|
|||
// For EVMs, same private key will be used for borrowing-lending activity.
|
||||
// When introducing other chains (e.g. Cosmos), need to do wallet registration
|
||||
// so we can access a map of a non-EVM address based on this EVM borrower
|
||||
NormalizedAmounts memory normalized = state.accountAssets[account];
|
||||
SourceTargetUints memory normalized = state.accountAssets[account];
|
||||
|
||||
// denormalize
|
||||
uint256 denormalizedDeposited = denormalizeAmount(
|
||||
normalized.sourceDeposited,
|
||||
collateralInterestAccrualIndex()
|
||||
normalized.source.deposited,
|
||||
sourceCollateralInterestAccrualIndex()
|
||||
);
|
||||
uint256 denormalizedBorrowed = denormalizeAmount(
|
||||
normalized.targetBorrowed,
|
||||
borrowedInterestAccrualIndex()
|
||||
normalized.target.borrowed,
|
||||
targetBorrowedInterestAccrualIndex()
|
||||
);
|
||||
|
||||
return
|
||||
|
@ -158,16 +175,16 @@ contract CrossChainBorrowLendGetters is Context, CrossChainBorrowLendState {
|
|||
// For EVMs, same private key will be used for borrowing-lending activity.
|
||||
// When introducing other chains (e.g. Cosmos), need to do wallet registration
|
||||
// so we can access a map of a non-EVM address based on this EVM borrower
|
||||
NormalizedAmounts memory normalized = state.accountAssets[account];
|
||||
SourceTargetUints memory normalized = state.accountAssets[account];
|
||||
|
||||
// denormalize
|
||||
uint256 denormalizedDeposited = denormalizeAmount(
|
||||
normalized.sourceDeposited,
|
||||
collateralInterestAccrualIndex()
|
||||
normalized.source.deposited,
|
||||
sourceCollateralInterestAccrualIndex()
|
||||
);
|
||||
uint256 denormalizedBorrowed = denormalizeAmount(
|
||||
normalized.targetBorrowed,
|
||||
borrowedInterestAccrualIndex()
|
||||
normalized.target.borrowed,
|
||||
targetBorrowedInterestAccrualIndex()
|
||||
);
|
||||
|
||||
return
|
||||
|
|
|
@ -16,7 +16,7 @@ contract CrossChainBorrowLendMessages {
|
|||
{
|
||||
return
|
||||
abi.encodePacked(
|
||||
header.borrower,
|
||||
header.sender,
|
||||
header.collateralAddress,
|
||||
header.borrowAddress
|
||||
);
|
||||
|
@ -77,6 +77,20 @@ contract CrossChainBorrowLendMessages {
|
|||
);
|
||||
}
|
||||
|
||||
function encodeDepositChangeMessage(DepositChangeMessage memory message)
|
||||
internal
|
||||
pure
|
||||
returns (bytes memory)
|
||||
{
|
||||
return
|
||||
abi.encodePacked(
|
||||
uint8(5), // payloadID
|
||||
encodeMessageHeader(message.header),
|
||||
uint8(message.depositType),
|
||||
message.amount
|
||||
);
|
||||
}
|
||||
|
||||
function decodeMessageHeader(bytes memory serialized)
|
||||
internal
|
||||
pure
|
||||
|
@ -86,7 +100,7 @@ contract CrossChainBorrowLendMessages {
|
|||
|
||||
// parse the header
|
||||
header.payloadID = serialized.toUint8(index += 1);
|
||||
header.borrower = serialized.toAddress(index += 20);
|
||||
header.sender = serialized.toAddress(index += 20);
|
||||
header.collateralAddress = serialized.toAddress(index += 20);
|
||||
header.borrowAddress = serialized.toAddress(index += 20);
|
||||
}
|
||||
|
@ -165,4 +179,34 @@ contract CrossChainBorrowLendMessages {
|
|||
require(params.header.payloadID == 4, "invalid message");
|
||||
require(index == serialized.length, "index != serialized.length");
|
||||
}
|
||||
|
||||
function decodeDepositChangeMessage(bytes memory serialized)
|
||||
internal
|
||||
pure
|
||||
returns (DepositChangeMessage memory params)
|
||||
{
|
||||
uint256 index = 0;
|
||||
|
||||
// parse the message header
|
||||
params.header = decodeMessageHeader(
|
||||
serialized.slice(index, index += 61)
|
||||
);
|
||||
|
||||
// handle DepositType enum value
|
||||
uint8 depositTypeValue = serialized.toUint8(index += 1);
|
||||
if (depositTypeValue == uint8(DepositType.Add)) {
|
||||
params.depositType = DepositType.Add;
|
||||
} else if (depositTypeValue == uint8(DepositType.Remove)) {
|
||||
params.depositType = DepositType.Remove;
|
||||
} else if (depositTypeValue == uint8(DepositType.RemoveFull)) {
|
||||
params.depositType = DepositType.RemoveFull;
|
||||
}
|
||||
else {
|
||||
revert("unrecognized deposit type");
|
||||
}
|
||||
params.amount = serialized.toUint256(index += 32);
|
||||
|
||||
require(params.header.payloadID == 5, "invalid message");
|
||||
require(index == serialized.length, "index != serialized.length");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {NormalizedAmounts, NormalizedTotalAmounts, InterestRateModel} from "./CrossChainBorrowLendStructs.sol";
|
||||
import "./CrossChainBorrowLendStructs.sol";
|
||||
|
||||
contract CrossChainBorrowLendStorage {
|
||||
struct State {
|
||||
|
@ -20,12 +20,12 @@ contract CrossChainBorrowLendStorage {
|
|||
bytes32 collateralAssetPythId;
|
||||
uint256 collateralizationRatio;
|
||||
address borrowingAssetAddress;
|
||||
uint256 interestAccrualIndex;
|
||||
SourceTargetUints interestAccrualIndex;
|
||||
uint256 interestAccrualIndexPrecision;
|
||||
uint256 lastActivityBlockTimestamp;
|
||||
NormalizedTotalAmounts totalAssets;
|
||||
SourceTargetUints totalAssets;
|
||||
uint256 repayGracePeriod;
|
||||
mapping(address => NormalizedAmounts) accountAssets;
|
||||
mapping(address => SourceTargetUints) accountAssets;
|
||||
bytes32 borrowingAssetPythId;
|
||||
mapping(bytes32 => bool) consumedMessages;
|
||||
InterestRateModel interestRateModel;
|
||||
|
|
|
@ -1,27 +1,27 @@
|
|||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
enum DepositType {
|
||||
None,
|
||||
Add,
|
||||
Remove,
|
||||
RemoveFull
|
||||
}
|
||||
|
||||
struct DepositedBorrowedUints {
|
||||
uint256 deposited;
|
||||
uint256 borrowed;
|
||||
}
|
||||
|
||||
struct NormalizedTotalAmounts {
|
||||
uint256 deposited;
|
||||
uint256 borrowed;
|
||||
}
|
||||
|
||||
struct NormalizedAmounts {
|
||||
uint256 sourceDeposited;
|
||||
uint256 sourceBorrowed;
|
||||
uint256 targetDeposited;
|
||||
uint256 targetBorrowed;
|
||||
struct SourceTargetUints {
|
||||
DepositedBorrowedUints source;
|
||||
DepositedBorrowedUints target;
|
||||
}
|
||||
|
||||
struct MessageHeader {
|
||||
uint8 payloadID;
|
||||
// address of the borrower
|
||||
address borrower;
|
||||
// address of the sender
|
||||
address sender;
|
||||
// collateral info
|
||||
address collateralAddress; // for verification
|
||||
// borrow info
|
||||
|
@ -58,9 +58,17 @@ struct LiquidationIntentMessage {
|
|||
// TODO: add necessary variables
|
||||
}
|
||||
|
||||
struct DepositChangeMessage {
|
||||
// payloadID = 5
|
||||
MessageHeader header;
|
||||
DepositType depositType;
|
||||
uint256 amount;
|
||||
}
|
||||
|
||||
struct InterestRateModel {
|
||||
uint64 ratePrecision;
|
||||
uint64 rateIntercept;
|
||||
uint64 rateCoefficientA;
|
||||
// TODO: add more complexity for example?
|
||||
uint64 reserveFactor;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
pragma solidity ^0.8.0;
|
||||
|
||||
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import {NormalizedAmounts, NormalizedTotalAmounts} from "../src/CrossChainBorrowLendStructs.sol";
|
||||
import "../src/CrossChainBorrowLendStructs.sol";
|
||||
import {ExposedCrossChainBorrowLend} from "./helpers/ExposedCrossChainBorrowLend.sol";
|
||||
import {MyERC20} from "./helpers/MyERC20.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
@ -46,7 +46,7 @@ contract CrossChainBorrowLendTest is Test {
|
|||
);
|
||||
}
|
||||
|
||||
function testComputeInterestProportion() public {
|
||||
function testSourceComputeInterestFactor() public {
|
||||
// start from zero
|
||||
vm.warp(0);
|
||||
uint256 timeStart = block.timestamp;
|
||||
|
@ -59,75 +59,125 @@ contract CrossChainBorrowLendTest is Test {
|
|||
uint256 intercept = 0.02e18; // 2% starting rate
|
||||
uint256 coefficient = 0.001e18; // increase 10 basis points per 1% borrowed
|
||||
|
||||
// fake supply some amount
|
||||
uint256 deposited = 100e6; // 100 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setTotalAssetsDeposited(deposited);
|
||||
|
||||
// fake borrow some amount
|
||||
uint256 borrowed = 50e6; // 50 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setTotalAssetsBorrowed(borrowed);
|
||||
|
||||
// we expect the interest accrued equal to the intercept
|
||||
uint256 interestProportion = borrowLendContract
|
||||
.EXPOSED_computeInterestProportion(
|
||||
secondsElapsed,
|
||||
intercept,
|
||||
coefficient
|
||||
);
|
||||
// set state for test
|
||||
uint256 sourceDeposited = 1e18; // 1 WBNB (18 decimals)
|
||||
uint256 sourceBorrowed = 0.5e18; // 0.5 WBNB (18 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssets(
|
||||
msg.sender,
|
||||
sourceDeposited,
|
||||
sourceBorrowed,
|
||||
0, // targetDeposited
|
||||
0 // targetBorrowed
|
||||
);
|
||||
|
||||
// expect using the correct value (0.0205e18)
|
||||
{
|
||||
require(
|
||||
interestProportion == 0.0205e18,
|
||||
"interestProportion != expected"
|
||||
borrowLendContract.EXPOSED_computeSourceInterestFactor(
|
||||
secondsElapsed,
|
||||
intercept,
|
||||
coefficient
|
||||
) == 0.0205e18,
|
||||
"computeSourceInterestFactor(...) != expected"
|
||||
);
|
||||
}
|
||||
|
||||
// expect using calculation
|
||||
{
|
||||
uint256 expected = intercept + (coefficient * borrowed) / deposited;
|
||||
require(
|
||||
interestProportion == expected,
|
||||
"interestProportion != expected (computed)"
|
||||
borrowLendContract.EXPOSED_computeTargetInterestFactor(
|
||||
secondsElapsed,
|
||||
intercept,
|
||||
coefficient
|
||||
) == 0,
|
||||
"computeTargetInterestFactor(...) != expected"
|
||||
);
|
||||
}
|
||||
|
||||
// clear
|
||||
borrowLendContract.HACKED_setTotalAssetsDeposited(0);
|
||||
borrowLendContract.HACKED_setTotalAssetsBorrowed(0);
|
||||
borrowLendContract.HACKED_resetAccountAssets(msg.sender);
|
||||
}
|
||||
|
||||
function testUpdateInterestAccrualIndex() public {
|
||||
function testTargetComputeInterestFactor() public {
|
||||
// start from zero
|
||||
vm.warp(0);
|
||||
uint256 timeStart = block.timestamp;
|
||||
|
||||
// warp to 1 year in the future
|
||||
vm.warp(365 * 24 * 60 * 60);
|
||||
uint256 secondsElapsed = block.timestamp - timeStart;
|
||||
|
||||
// accrue interest with intercept and coefficient
|
||||
uint256 intercept = 0.02e18; // 2% starting rate
|
||||
uint256 coefficient = 0.001e18; // increase 10 basis points per 1% borrowed
|
||||
|
||||
// set state for test
|
||||
uint256 targetDeposited = 100e6; // 100 USDC (6 decimals)
|
||||
uint256 targetBorrowed = 50e6; // 50 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssets(
|
||||
msg.sender,
|
||||
0, // sourceDeposited
|
||||
0, // sourceBorrowed
|
||||
targetDeposited,
|
||||
targetBorrowed
|
||||
);
|
||||
|
||||
// expect using the correct value (0.0205e18)
|
||||
{
|
||||
require(
|
||||
borrowLendContract.EXPOSED_computeSourceInterestFactor(
|
||||
secondsElapsed,
|
||||
intercept,
|
||||
coefficient
|
||||
) == 0,
|
||||
"computeSourceInterestFactor(...) != expected"
|
||||
);
|
||||
require(
|
||||
borrowLendContract.EXPOSED_computeTargetInterestFactor(
|
||||
secondsElapsed,
|
||||
intercept,
|
||||
coefficient
|
||||
) == 0.0205e18,
|
||||
"computeTargetInterestFactor(...) != expected"
|
||||
);
|
||||
}
|
||||
|
||||
// clear
|
||||
borrowLendContract.HACKED_resetAccountAssets(msg.sender);
|
||||
}
|
||||
|
||||
function testUpdateSourceInterestAccrualIndex() public {
|
||||
// start from zero
|
||||
vm.warp(0);
|
||||
borrowLendContract.HACKED_setLastActivityBlockTimestamp(
|
||||
block.timestamp
|
||||
);
|
||||
|
||||
// fake supply some amount
|
||||
uint256 deposited = 200e6; // 200 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setTotalAssetsDeposited(deposited);
|
||||
|
||||
// fake borrow some amount
|
||||
uint256 borrowed = 20e6; // 20 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setTotalAssetsBorrowed(borrowed);
|
||||
// set state for test
|
||||
uint256 sourceDeposited = 200e18; // 200 WBNB (18 decimals)
|
||||
uint256 sourceBorrowed = 20e18; // 20 WBNB (18 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssets(
|
||||
msg.sender,
|
||||
sourceDeposited,
|
||||
sourceBorrowed,
|
||||
0, // targetDeposited
|
||||
0 // targetBorrowed
|
||||
);
|
||||
|
||||
// warp to 1 year in the future
|
||||
vm.warp(365 * 24 * 60 * 60);
|
||||
|
||||
// trigger accrual
|
||||
borrowLendContract.EXPOSED_updateInterestAccrualIndex();
|
||||
borrowLendContract.EXPOSED_updateSourceInterestAccrualIndex();
|
||||
|
||||
{
|
||||
// expect using the correct value (1.02e18)
|
||||
require(
|
||||
borrowLendContract.borrowedInterestAccrualIndex() == 1.02e18,
|
||||
"borrowedInterestAccrualIndex() != expected (first iteration)"
|
||||
borrowLendContract.sourceBorrowedInterestAccrualIndex() ==
|
||||
1.02e18,
|
||||
"sourceBorrowedInterestAccrualIndex() != expected (first iteration)"
|
||||
);
|
||||
// expect using the correct value (1.002e18)
|
||||
require(
|
||||
borrowLendContract.collateralInterestAccrualIndex() == 1.002e18,
|
||||
"collateralInterestAccrualIndex() != expected (first iteration)"
|
||||
borrowLendContract.sourceCollateralInterestAccrualIndex() ==
|
||||
1.002e18,
|
||||
"sourceBollateralInterestAccrualIndex() != expected (first iteration)"
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -135,35 +185,38 @@ contract CrossChainBorrowLendTest is Test {
|
|||
vm.warp(2 * 365 * 24 * 60 * 60);
|
||||
|
||||
// trigger accrual again
|
||||
borrowLendContract.EXPOSED_updateInterestAccrualIndex();
|
||||
borrowLendContract.EXPOSED_updateSourceInterestAccrualIndex();
|
||||
|
||||
{
|
||||
// expect using the correct value (1.04e18)
|
||||
require(
|
||||
borrowLendContract.borrowedInterestAccrualIndex() == 1.04e18,
|
||||
"borrowedInterestAccrualIndex() != expected (second iteration)"
|
||||
borrowLendContract.sourceBorrowedInterestAccrualIndex() ==
|
||||
1.04e18,
|
||||
"sourceBorrowedInterestAccrualIndex() != expected (second iteration)"
|
||||
);
|
||||
// expect using the correct value (1.004e18)
|
||||
require(
|
||||
borrowLendContract.collateralInterestAccrualIndex() == 1.004e18,
|
||||
"collateralInterestAccrualIndex() != expected (second iteration)"
|
||||
borrowLendContract.sourceCollateralInterestAccrualIndex() ==
|
||||
1.004e18,
|
||||
"sourceCollateralInterestAccrualIndex() != expected (second iteration)"
|
||||
);
|
||||
}
|
||||
|
||||
// check denormalized deposit and borrowed. should be equal
|
||||
{
|
||||
NormalizedTotalAmounts memory amounts = borrowLendContract
|
||||
.normalizedAmounts();
|
||||
DepositedBorrowedUints memory amounts = borrowLendContract
|
||||
.normalizedAmounts()
|
||||
.source;
|
||||
uint256 accruedDepositedInterest = borrowLendContract
|
||||
.denormalizeAmount(
|
||||
amounts.deposited,
|
||||
borrowLendContract.collateralInterestAccrualIndex()
|
||||
) - deposited;
|
||||
borrowLendContract.sourceCollateralInterestAccrualIndex()
|
||||
) - sourceDeposited;
|
||||
uint256 accruedBorrowedInterest = borrowLendContract
|
||||
.denormalizeAmount(
|
||||
amounts.borrowed,
|
||||
borrowLendContract.borrowedInterestAccrualIndex()
|
||||
) - borrowed;
|
||||
borrowLendContract.sourceBorrowedInterestAccrualIndex()
|
||||
) - sourceBorrowed;
|
||||
require(
|
||||
accruedDepositedInterest == accruedBorrowedInterest,
|
||||
"accruedDepositedInterest != accruedBorrowedInterest"
|
||||
|
@ -171,24 +224,106 @@ contract CrossChainBorrowLendTest is Test {
|
|||
}
|
||||
|
||||
// clear
|
||||
borrowLendContract.HACKED_setTotalAssetsDeposited(0);
|
||||
borrowLendContract.HACKED_setTotalAssetsBorrowed(0);
|
||||
borrowLendContract.HACKED_resetAccountAssets(msg.sender);
|
||||
}
|
||||
|
||||
function testUpdateTargetInterestAccrualIndex() public {
|
||||
// start from zero
|
||||
vm.warp(0);
|
||||
borrowLendContract.HACKED_setLastActivityBlockTimestamp(
|
||||
block.timestamp
|
||||
);
|
||||
|
||||
// set state for test
|
||||
uint256 targetDeposited = 200e6; // 200 USDC (6 decimals)
|
||||
uint256 targetBorrowed = 20e6; // 20 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssets(
|
||||
msg.sender,
|
||||
0, // sourceDeposited
|
||||
0, // sourceBorrowed
|
||||
targetDeposited,
|
||||
targetBorrowed
|
||||
);
|
||||
|
||||
// warp to 1 year in the future
|
||||
vm.warp(365 * 24 * 60 * 60);
|
||||
|
||||
// trigger accrual
|
||||
borrowLendContract.EXPOSED_updateTargetInterestAccrualIndex();
|
||||
|
||||
{
|
||||
// expect using the correct value (1.02e18)
|
||||
require(
|
||||
borrowLendContract.targetBorrowedInterestAccrualIndex() ==
|
||||
1.02e18,
|
||||
"targetBorrowedInterestAccrualIndex() != expected (first iteration)"
|
||||
);
|
||||
// expect using the correct value (1.002e18)
|
||||
require(
|
||||
borrowLendContract.targetCollateralInterestAccrualIndex() ==
|
||||
1.002e18,
|
||||
"targetCollateralInterestAccrualIndex() != expected (first iteration)"
|
||||
);
|
||||
}
|
||||
|
||||
// warp to 2 years in the future
|
||||
vm.warp(2 * 365 * 24 * 60 * 60);
|
||||
|
||||
// trigger accrual again
|
||||
borrowLendContract.EXPOSED_updateTargetInterestAccrualIndex();
|
||||
|
||||
{
|
||||
// expect using the correct value (1.04e18)
|
||||
require(
|
||||
borrowLendContract.targetBorrowedInterestAccrualIndex() ==
|
||||
1.04e18,
|
||||
"targetBorrowedInterestAccrualIndex() != expected (second iteration)"
|
||||
);
|
||||
// expect using the correct value (1.004e18)
|
||||
require(
|
||||
borrowLendContract.targetCollateralInterestAccrualIndex() ==
|
||||
1.004e18,
|
||||
"targetCollateralInterestAccrualIndex() != expected (second iteration)"
|
||||
);
|
||||
}
|
||||
|
||||
// check denormalized deposit and borrowed. should be equal
|
||||
{
|
||||
DepositedBorrowedUints memory amounts = borrowLendContract
|
||||
.normalizedAmounts()
|
||||
.target;
|
||||
uint256 accruedDepositedInterest = borrowLendContract
|
||||
.denormalizeAmount(
|
||||
amounts.deposited,
|
||||
borrowLendContract.sourceCollateralInterestAccrualIndex()
|
||||
) - targetDeposited;
|
||||
uint256 accruedBorrowedInterest = borrowLendContract
|
||||
.denormalizeAmount(
|
||||
amounts.borrowed,
|
||||
borrowLendContract.sourceBorrowedInterestAccrualIndex()
|
||||
) - targetBorrowed;
|
||||
require(
|
||||
accruedDepositedInterest == accruedBorrowedInterest,
|
||||
"accruedDepositedInterest != accruedBorrowedInterest"
|
||||
);
|
||||
}
|
||||
|
||||
// clear
|
||||
borrowLendContract.HACKED_resetAccountAssets(msg.sender);
|
||||
}
|
||||
|
||||
function testMaxAllowedToWithdraw() public {
|
||||
uint64 collateralPrice = 400; // WBNB
|
||||
uint64 borrowAssetPrice = 1; // USDC
|
||||
|
||||
uint256 deposited = 1e18; // 1 WBNB (18 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssetsDeposited(
|
||||
uint256 sourceDeposited = 1e18; // 1 WBNB (18 decimals)
|
||||
uint256 targetBorrowed = 100e6; // 100 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssets(
|
||||
msg.sender,
|
||||
deposited
|
||||
);
|
||||
|
||||
uint256 borrowed = 100e6; // 100 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssetsBorrowed(
|
||||
msg.sender,
|
||||
borrowed
|
||||
sourceDeposited,
|
||||
0, // sourceBorrowed
|
||||
0, // targetDeposited
|
||||
targetBorrowed
|
||||
);
|
||||
|
||||
uint256 maxAllowed = borrowLendContract
|
||||
|
@ -204,24 +339,21 @@ contract CrossChainBorrowLendTest is Test {
|
|||
}
|
||||
|
||||
// clear
|
||||
borrowLendContract.HACKED_setAccountAssetsDeposited(msg.sender, 0);
|
||||
borrowLendContract.HACKED_setAccountAssetsBorrowed(msg.sender, 0);
|
||||
borrowLendContract.HACKED_resetAccountAssets(msg.sender);
|
||||
}
|
||||
|
||||
function testMaxAllowedToBorrow() public {
|
||||
uint64 collateralPrice = 400; // WBNB
|
||||
uint64 borrowAssetPrice = 1; // USDC
|
||||
|
||||
uint256 deposited = 1e18; // 1 WBNB (18 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssetsDeposited(
|
||||
uint256 sourceDeposited = 1e18; // 1 WBNB (18 decimals)
|
||||
uint256 targetBorrowed = 100e6; // 100 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssets(
|
||||
msg.sender,
|
||||
deposited
|
||||
);
|
||||
|
||||
uint256 borrowed = 100e6; // 100 USDC (6 decimals)
|
||||
borrowLendContract.HACKED_setAccountAssetsBorrowed(
|
||||
msg.sender,
|
||||
borrowed
|
||||
sourceDeposited,
|
||||
0, // sourceBorrowed
|
||||
0, // targetDeposited
|
||||
targetBorrowed
|
||||
);
|
||||
|
||||
uint256 maxAllowed = borrowLendContract
|
||||
|
@ -237,7 +369,6 @@ contract CrossChainBorrowLendTest is Test {
|
|||
}
|
||||
|
||||
// clear
|
||||
borrowLendContract.HACKED_setAccountAssetsDeposited(msg.sender, 0);
|
||||
borrowLendContract.HACKED_setAccountAssetsBorrowed(msg.sender, 0);
|
||||
borrowLendContract.HACKED_resetAccountAssets(msg.sender);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import {NormalizedAmounts} from "../../src/CrossChainBorrowLendStructs.sol";
|
||||
import "../../src/CrossChainBorrowLendStructs.sol";
|
||||
import {CrossChainBorrowLend} from "../../src/CrossChainBorrowLend.sol";
|
||||
import "forge-std/Test.sol";
|
||||
|
||||
|
@ -39,17 +39,30 @@ contract ExposedCrossChainBorrowLend is CrossChainBorrowLend {
|
|||
// nothing else
|
||||
}
|
||||
|
||||
function EXPOSED_computeInterestProportion(
|
||||
function EXPOSED_computeSourceInterestFactor(
|
||||
uint256 secondsElapsed,
|
||||
uint256 intercept,
|
||||
uint256 coefficient
|
||||
) external view returns (uint256) {
|
||||
return
|
||||
computeInterestProportion(secondsElapsed, intercept, coefficient);
|
||||
computeSourceInterestFactor(secondsElapsed, intercept, coefficient);
|
||||
}
|
||||
|
||||
function EXPOSED_updateInterestAccrualIndex() external {
|
||||
return updateInterestAccrualIndex();
|
||||
function EXPOSED_computeTargetInterestFactor(
|
||||
uint256 secondsElapsed,
|
||||
uint256 intercept,
|
||||
uint256 coefficient
|
||||
) external view returns (uint256) {
|
||||
return
|
||||
computeTargetInterestFactor(secondsElapsed, intercept, coefficient);
|
||||
}
|
||||
|
||||
function EXPOSED_updateSourceInterestAccrualIndex() external {
|
||||
return updateSourceInterestAccrualIndex();
|
||||
}
|
||||
|
||||
function EXPOSED_updateTargetInterestAccrualIndex() external {
|
||||
return updateTargetInterestAccrualIndex();
|
||||
}
|
||||
|
||||
function EXPOSED_maxAllowedToBorrowWithPrices(
|
||||
|
@ -81,32 +94,35 @@ contract ExposedCrossChainBorrowLend is CrossChainBorrowLend {
|
|||
function EXPOSED_accountAssets(address account)
|
||||
external
|
||||
view
|
||||
returns (NormalizedAmounts memory)
|
||||
returns (SourceTargetUints memory)
|
||||
{
|
||||
return state.accountAssets[account];
|
||||
}
|
||||
|
||||
function HACKED_setTotalAssetsDeposited(uint256 amount) external {
|
||||
state.totalAssets.deposited = amount;
|
||||
function HACKED_setAccountAssets(
|
||||
address account,
|
||||
uint256 sourceDeposited,
|
||||
uint256 sourceBorrowed,
|
||||
uint256 targetDeposited,
|
||||
uint256 targetBorrowed
|
||||
) public {
|
||||
// account
|
||||
state.accountAssets[account].source.deposited = sourceDeposited;
|
||||
state.accountAssets[account].source.borrowed = sourceBorrowed;
|
||||
state.accountAssets[account].target.deposited = targetDeposited;
|
||||
state.accountAssets[account].target.borrowed = targetBorrowed;
|
||||
// total
|
||||
state.totalAssets.source.deposited = sourceDeposited;
|
||||
state.totalAssets.source.borrowed = sourceBorrowed;
|
||||
state.totalAssets.target.deposited = targetDeposited;
|
||||
state.totalAssets.target.borrowed = targetBorrowed;
|
||||
}
|
||||
|
||||
function HACKED_setTotalAssetsBorrowed(uint256 amount) external {
|
||||
state.totalAssets.borrowed = amount;
|
||||
function HACKED_resetAccountAssets(address account) public {
|
||||
HACKED_setAccountAssets(account, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
function HACKED_setLastActivityBlockTimestamp(uint256 timestamp) external {
|
||||
function HACKED_setLastActivityBlockTimestamp(uint256 timestamp) public {
|
||||
state.lastActivityBlockTimestamp = timestamp;
|
||||
}
|
||||
|
||||
function HACKED_setAccountAssetsDeposited(address account, uint256 amount)
|
||||
external
|
||||
{
|
||||
state.accountAssets[account].sourceDeposited = amount;
|
||||
}
|
||||
|
||||
function HACKED_setAccountAssetsBorrowed(address account, uint256 amount)
|
||||
external
|
||||
{
|
||||
state.accountAssets[account].targetBorrowed = amount;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue