Skip to content

Instantly share code, notes, and snippets.

@pi0neerpat
Last active May 10, 2021 17:19
Show Gist options
  • Save pi0neerpat/2d2c1e51a21ff5496c69397454c1eee5 to your computer and use it in GitHub Desktop.
Save pi0neerpat/2d2c1e51a21ff5496c69397454c1eee5 to your computer and use it in GitHub Desktop.
Superfluid App Example: tradeable-cashflow
// 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";
// When ready to move to leave Remix, change imports to follow this pattern:
// "@superfluid-finance/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";
contract RedirectAll is SuperAppBase {
ISuperfluid private _host; // host
IConstantFlowAgreementV1 private _cfa; // the stored constant flow agreement class address
ISuperToken private _acceptedToken; // accepted token
address private _receiver;
constructor(
ISuperfluid host,
IConstantFlowAgreementV1 cfa,
ISuperToken acceptedToken,
address receiver) {
assert(address(host) != address(0));
assert(address(cfa) != address(0));
assert(address(acceptedToken) != address(0));
assert(address(receiver) != address(0));
//assert(!_host.isApp(ISuperApp(receiver)));
_host = host;
_cfa = cfa;
_acceptedToken = acceptedToken;
_receiver = receiver;
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 currentReceiver()
external view
returns (
uint256 startTime,
address receiver,
int96 flowRate
)
{
if (_receiver != address(0)) {
(startTime, flowRate,,) = _cfa.getFlow(_acceptedToken, address(this), _receiver);
receiver = _receiver;
}
}
event ReceiverChanged(address receiver); //what is this?
/// @dev If a new stream is opened, or an existing one is opened
function _updateOutflow(bytes calldata ctx)
private
returns (bytes memory newCtx)
{
newCtx = ctx;
// @dev This will give me the new flowRate, as it is called in after callbacks
int96 netFlowRate = _cfa.getNetFlow(_acceptedToken, address(this));
(,int96 outFlowRate,,) = _cfa.getFlow(_acceptedToken, address(this), _receiver);
int96 inFlowRate = netFlowRate + outFlowRate;
if (inFlowRate < 0 ) inFlowRate = -inFlowRate; // Fixes issue when inFlowRate is negative
// @dev If inFlowRate === 0, then delete existing flow.
if (outFlowRate != int96(0)){
(newCtx, ) = _host.callAgreementWithContext(
_cfa,
abi.encodeWithSelector(
_cfa.updateFlow.selector,
_acceptedToken,
_receiver,
inFlowRate,
new bytes(0) // placeholder
),
"0x",
newCtx
);
} else if (inFlowRate == int96(0)) {
// @dev if inFlowRate is zero, delete outflow.
(newCtx, ) = _host.callAgreementWithContext(
_cfa,
abi.encodeWithSelector(
_cfa.deleteFlow.selector,
_acceptedToken,
address(this),
_receiver,
new bytes(0) // placeholder
),
"0x",
newCtx
);
} else {
// @dev If there is no existing outflow, then create new flow to equal inflow
(newCtx, ) = _host.callAgreementWithContext(
_cfa,
abi.encodeWithSelector(
_cfa.createFlow.selector,
_acceptedToken,
_receiver,
inFlowRate,
new bytes(0) // placeholder
),
"0x",
newCtx
);
}
}
// @dev Change the Receiver of the total flow
function _changeReceiver( address newReceiver ) internal {
require(newReceiver != address(0), "New receiver is zero address");
// @dev because our app is registered as final, we can't take downstream apps
require(!_host.isApp(ISuperApp(newReceiver)), "New receiver can not be a superApp");
if (newReceiver == _receiver) return ;
// @dev delete flow to old receiver
_host.callAgreement(
_cfa,
abi.encodeWithSelector(
_cfa.deleteFlow.selector,
_acceptedToken,
address(this),
_receiver,
new bytes(0)
),
"0x"
);
// @dev create flow to new receiver
_host.callAgreement(
_cfa,
abi.encodeWithSelector(
_cfa.createFlow.selector,
_acceptedToken,
newReceiver,
_cfa.getNetFlow(_acceptedToken, address(this)),
new bytes(0)
),
"0x"
);
// @dev set global receiver to new receiver
_receiver = newReceiver;
emit ReceiverChanged(_receiver);
}
/**************************************************************************
* SuperApp callbacks
*************************************************************************/
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");
_;
}
}
//SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import {RedirectAll, ISuperToken, IConstantFlowAgreementV1, ISuperfluid} from "./RedirectAll.sol";
import {ERC721} from "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v3.2.0-solc-0.7/contracts/token/ERC721/ERC721.sol";
/* Hello and welcome to your first Super App!
* In order to deploy this contract, you'll need a few things
* Get the deployed SF addresses here: https://docs.superfluid.finance/superfluid/resources/networks
* or using the js-sdk as shown here https://docs.superfluid.finance/superfluid/protocol-tutorials/setup-local-environment
*/
contract TradeableCashflow is ERC721, RedirectAll {
constructor (
address owner,
string memory _name,
string memory _symbol,
ISuperfluid host,
IConstantFlowAgreementV1 cfa,
ISuperToken acceptedToken
)
ERC721 ( _name, _symbol )
RedirectAll (
host,
cfa,
acceptedToken,
owner
)
{
_mint(owner, 1);
}
//now I will insert a nice little hook in the _transfer, including the RedirectAll function I need
function _beforeTokenTransfer(
address /*from*/,
address to,
uint256 /*tokenId*/
) internal override {
_changeReceiver(to);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment