Created
October 5, 2021 03:20
-
-
Save J-Priebe/7473d499a90fcfcd2ea89b659a421885 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.7.0+commit.9e61f92b.js&optimize=false&runs=200&gist=
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
pragma solidity >= 0.7.0; | |
import { | |
ISuperfluid, | |
ISuperToken, | |
ISuperApp, | |
ISuperAgreement, | |
SuperAppDefinitions | |
} from "https://github.com/superfluid-finance/protocol-monorepo/blob/remix-support/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol"; | |
import { | |
IConstantFlowAgreementV1 | |
} from "https://github.com/superfluid-finance/protocol-monorepo/blob/remix-support/packages/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol"; | |
import { | |
SuperAppBase | |
} from "https://github.com/superfluid-finance/protocol-monorepo/blob/remix-support/packages/ethereum-contracts/contracts/apps/SuperAppBase.sol"; | |
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.4-solc-0.7/contracts/math/Math.sol"; | |
contract RepaymentStream is SuperAppBase { | |
address public _lender; | |
address public _borrower; | |
int96 public _repaymentPercent; | |
uint public _repaymentAmount; | |
// uint private _totalRepaid; | |
ISuperfluid private _host; // host | |
IConstantFlowAgreementV1 private _cfa; // the stored constant flow agreement class address | |
ISuperToken private _acceptedToken; // accepted token | |
constructor( | |
address borrower, | |
address lender, | |
int96 repaymentPercent, | |
uint repaymentAmount, | |
ISuperfluid host, | |
IConstantFlowAgreementV1 cfa, | |
ISuperToken acceptedToken | |
) { | |
require(repaymentPercent >= 1 && repaymentPercent <= 100); | |
require(lender != borrower); | |
assert(address(host) != address(0)); | |
assert(address(cfa) != address(0)); | |
assert(address(acceptedToken) != address(0)); | |
assert(address(lender) != address(0)); | |
//assert(!_host.isApp(ISuperApp(lender))); | |
_borrower = borrower; | |
_lender = lender; | |
_repaymentPercent = repaymentPercent; | |
_repaymentAmount = repaymentAmount; | |
_host = host; | |
_cfa = cfa; | |
_acceptedToken = acceptedToken; | |
// Super App constants. These are events upon which the host calls our contract | |
uint256 configWord = | |
SuperAppDefinitions.APP_LEVEL_FINAL | | |
SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP | | |
SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP | | |
SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP; | |
_host.registerApp(configWord); | |
} | |
/************************************************************************** | |
* Redirect Logic | |
*************************************************************************/ | |
function currentLender() | |
external view | |
returns ( | |
uint256 startTime, | |
address lender, | |
int96 flowRate | |
) | |
{ | |
if (_lender != address(0)) { | |
(startTime, flowRate,,) = _cfa.getFlow(_acceptedToken, address(this), _lender); | |
lender = _lender; | |
} | |
} | |
function getFlowRates() public view returns ( | |
int96 inFlowRate, int96 lenderOutFlowRate, int96 borrowerOutFlowRate | |
) { | |
int96 _netFlowRate = _cfa.getNetFlow(_acceptedToken, address(this)); | |
(,int96 _lenderOutFlowRate,,) = _cfa.getFlow(_acceptedToken, address(this), _lender); | |
(,int96 _borrowerOutFlowRate,,) = _cfa.getFlow(_acceptedToken, address(this), _borrower); | |
int96 _rate = _netFlowRate + _lenderOutFlowRate + _borrowerOutFlowRate; | |
if (_rate < 0 ) _rate = -_rate; | |
return (_rate, _lenderOutFlowRate, _borrowerOutFlowRate); | |
} | |
function getNewRates(int96 totalRate) public view returns ( | |
int96 newLenderRate, int96 newBorrowerRate | |
) { | |
int96 newLenderFlowRate = (totalRate * _repaymentPercent) / 100 ; | |
int96 newBorrowerFlowRate = int96(Math.max(uint256(totalRate) - uint256(newLenderFlowRate), uint256(0))); // potential rounding issue | |
return (newLenderFlowRate, newBorrowerFlowRate); | |
} | |
function amountRepaid() external view returns ( | |
int256 balance | |
) { | |
(int256 _balance,,) = _cfa.realtimeBalanceOf(_acceptedToken, _lender, block.timestamp); | |
return _balance; | |
} | |
function ceaseRepayment() external { | |
// delete flow to lender, and set rate to borrower = 100% | |
(int256 balance,,) = _cfa.realtimeBalanceOf(_acceptedToken, _lender, block.timestamp); | |
require(uint(balance) >= _repaymentAmount, "Full amount has not been repaid yet"); | |
(int96 inFlowRate,,) = getFlowRates(); | |
_cfa.deleteFlow(_acceptedToken, address(this), _lender, new bytes(0)); | |
_cfa.updateFlow(_acceptedToken, _borrower, inFlowRate, new bytes(0)); | |
} | |
// @dev If a new stream is opened, or an existing one is updated | |
// remember - this contract is an intermediary, outflow should always | |
// equal inflow | |
function _updateOutflow(bytes calldata ctx) | |
private | |
returns (bytes memory newCtx) | |
{ | |
newCtx = ctx; | |
(int96 inFlowRate, int96 lenderOutFlowRate, int96 borrowerOutFlowRate) = getFlowRates(); | |
// STEP 1 | |
// Delete the current flows to the borrower and lender. | |
if (borrowerOutFlowRate != int96(0)) { | |
// Delete borrower flow | |
(newCtx, ) = _host.callAgreementWithContext( | |
_cfa, | |
abi.encodeWithSelector( | |
_cfa.deleteFlow.selector, | |
_acceptedToken, | |
address(this), | |
_borrower, | |
new bytes(0) // placeholder | |
), | |
"0x", | |
newCtx | |
); | |
} | |
if (lenderOutFlowRate != int96(0)) { | |
// Delete lender flow | |
(newCtx, ) = _host.callAgreementWithContext( | |
_cfa, | |
abi.encodeWithSelector( | |
_cfa.deleteFlow.selector, | |
_acceptedToken, | |
address(this), | |
_lender, | |
new bytes(0) // placeholder | |
), | |
"0x", | |
newCtx | |
); | |
} | |
// STEP 2 | |
// Calculate the new flow rates for borrower and lender. | |
if (inFlowRate > 0) { | |
(int96 newLenderFlowRate, int96 newBorrowerFlowRate) = getNewRates(inFlowRate); | |
// = (inFlowRate * _repaymentPercent) / 100 ; | |
//int96 newBorrowerFlowRate = inFlowRate - newLenderFlowRate; | |
assert(newLenderFlowRate + newBorrowerFlowRate == inFlowRate); | |
(newCtx, ) = _host.callAgreementWithContext( | |
_cfa, | |
abi.encodeWithSelector( | |
_cfa.createFlow.selector, | |
_acceptedToken, | |
_lender, | |
newLenderFlowRate, | |
new bytes(0) // placeholder | |
), | |
"0x", | |
newCtx | |
); | |
if (newBorrowerFlowRate > 0) { | |
(newCtx, ) = _host.callAgreementWithContext( | |
_cfa, | |
abi.encodeWithSelector( | |
_cfa.createFlow.selector, | |
_acceptedToken, | |
_borrower, | |
1, //newBorrowerFlowRate, | |
new bytes(0) // placeholder | |
), | |
"0x", | |
newCtx | |
); | |
} | |
} | |
} | |
/************************************************************************** | |
* SuperApp callbacks | |
These are called when a new flow is created, updated, deleted. | |
(Remember: our app is a *Constant Flow Agreement*) | |
*************************************************************************/ | |
function afterAgreementCreated( | |
ISuperToken _superToken, | |
address _agreementClass, | |
bytes32, // _agreementId, | |
bytes calldata /*_agreementData*/, | |
bytes calldata ,// _cbdata, | |
bytes calldata _ctx | |
) | |
external override | |
onlyExpected(_superToken, _agreementClass) | |
onlyHost | |
returns (bytes memory newCtx) | |
{ | |
return _updateOutflow(_ctx); | |
} | |
function afterAgreementUpdated( | |
ISuperToken _superToken, | |
address _agreementClass, | |
bytes32 ,//_agreementId, | |
bytes calldata /*_agreementData*/, | |
bytes calldata ,//_cbdata, | |
bytes calldata _ctx | |
) | |
external override | |
onlyExpected(_superToken, _agreementClass) | |
onlyHost | |
returns (bytes memory newCtx) | |
{ | |
return _updateOutflow(_ctx); | |
} | |
function afterAgreementTerminated( | |
ISuperToken _superToken, | |
address _agreementClass, | |
bytes32 ,//_agreementId, | |
bytes calldata /*_agreementData*/, | |
bytes calldata ,//_cbdata, | |
bytes calldata _ctx | |
) | |
external override | |
onlyHost | |
returns (bytes memory newCtx) | |
{ | |
// According to the app basic law, we should never revert in a termination callback | |
if (!_isSameToken(_superToken) || !_isCFAv1(_agreementClass)) return _ctx; | |
return _updateOutflow(_ctx); | |
} | |
function _isSameToken(ISuperToken superToken) private view returns (bool) { | |
return address(superToken) == address(_acceptedToken); | |
} | |
function _isCFAv1(address agreementClass) private view returns (bool) { | |
return ISuperAgreement(agreementClass).agreementType() | |
== keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1"); | |
} | |
modifier onlyHost() { | |
require(msg.sender == address(_host), "RedirectAll: support only one host"); | |
_; | |
} | |
modifier onlyExpected(ISuperToken superToken, address agreementClass) { | |
require(_isSameToken(superToken), "RedirectAll: not accepted token"); | |
require(_isCFAv1(agreementClass), "RedirectAll: only CFAv1 supported"); | |
_; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment