Skip to content

Instantly share code, notes, and snippets.

@filipesoccol
Created September 26, 2021 19:50
Show Gist options
  • Save filipesoccol/b5da569d96da76b7661d8c2dc4bb5b40 to your computer and use it in GitHub Desktop.
Save filipesoccol/b5da569d96da76b7661d8c2dc4bb5b40 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.8.0+commit.c7dfd78e.js&optimize=false&runs=200&gist=
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0;
// STEP 1: Import the right interfaces
import {
ISuperfluid,
ISuperToken,
ISuperApp,
ISuperAgreement,
SuperAppDefinitions
} from "https://github.com/superfluid-finance/protocol-monorepo/blob/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
import {
IConstantFlowAgreementV1
} from "https://github.com/superfluid-finance/protocol-monorepo/blob/dev/packages/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol";
import {
SuperAppBase
} from "https://github.com/superfluid-finance/protocol-monorepo/blob/dev/packages/ethereum-contracts/contracts/apps/SuperAppBase.sol";
// STEP 2: Create a Super App contract base using SuperAppBase (constants, properties and constructor)
/**
* @dev A simple stream swap demo exchange your token1 for token2 and vice versa
*/
contract AsteroidStream is SuperAppBase {
bytes32 private constant _AGREEMENT_TYPE_CFAv1 = keccak256("org.superfluid-finance.agreements.ConstantFlowAgreement.v1");
ISuperfluid private host;
IConstantFlowAgreementV1 private cfa;
ISuperToken private superToken1;
ISuperToken private superToken2;
address private creator;
int96 public maxFlow;
string public name;
event ExchangeHappened(ISuperToken inputToken, ISuperToken outputToken, int96 flowRate);
constructor(
ISuperfluid _host,
ISuperToken _superToken1,
ISuperToken _superToken2,
address _creator,
int96 _maxFlow,
string memory _name
) {
require(address(_host) != address(0), "host is nil");
require(address(_superToken1) != address(0), "superToken1 is nil");
require(address(_superToken2) != address(0), "superToken2 is nil");
host = _host;
cfa = IConstantFlowAgreementV1(address(host.getAgreementClass(_AGREEMENT_TYPE_CFAv1)));
superToken1 = _superToken1;
superToken2 = _superToken2;
creator = _creator;
name = _name;
maxFlow = _maxFlow;
uint256 configWord =
SuperAppDefinitions.APP_LEVEL_FINAL |
SuperAppDefinitions.BEFORE_AGREEMENT_CREATED_NOOP |
SuperAppDefinitions.BEFORE_AGREEMENT_UPDATED_NOOP |
SuperAppDefinitions.BEFORE_AGREEMENT_TERMINATED_NOOP;
host.registerApp(configWord);
}
// STEP 3: The stream callbacks
/**************************************************************************
* 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)
{
return _doExchange(ctx, superToken, agreementData, agreementId);
}
function afterAgreementTerminated(
ISuperToken superToken,
address agreementClass,
bytes32 agreementId,
bytes calldata agreementData,
bytes calldata /*cbdata*/,
bytes calldata ctx
)
external override
onlyHost
returns (bytes memory)
{
// According to the app basic law, we should never revert in a termination callback
if (!_isAccepted(superToken) || !_isCFAv1(agreementClass)) return ctx;
return _stopExchange(ctx, superToken, agreementData, agreementId);
}
function _doExchange(
bytes calldata _ctx,
ISuperToken inputToken,
bytes memory agreementData,
bytes32 agreementId
)
private
returns (bytes memory newCtx)
{
// get info about the stream
(address user, ) = abi.decode(agreementData, (address, address));
(,int96 flowRate,,) = cfa.getFlowByID(inputToken, agreementId);
// create flow
(newCtx, ) = host.callAgreementWithContext(
cfa,
abi.encodeWithSelector(
cfa.createFlow.selector,
superToken2,
user,
flowRate,
new bytes(0) // placeholder
),
new bytes(0), // user data
_ctx
);
emit ExchangeHappened(inputToken, superToken2, flowRate);
}
function _stopExchange(
bytes calldata _ctx,
ISuperToken inputToken,
bytes memory agreementData,
bytes32 /* agreementId */
)
private
returns (bytes memory newCtx)
{
// select the other token
ISuperToken outputToken;
if(address(inputToken) == address(superToken1)) outputToken = superToken2;
if(address(inputToken) == address(superToken2)) outputToken = superToken1;
// get infor about the flow
(address user, ) = abi.decode(agreementData, (address, address));
(,int96 flowRate,,) = cfa.getFlow(outputToken, address(this), user);
// delete flow
(newCtx,) = host.callAgreementWithContext(
cfa,
abi.encodeWithSelector(
cfa.deleteFlow.selector,
outputToken,
address(this),
user,
new bytes(0)
),
new bytes(0), // user data
_ctx
);
}
function withdrawDAI() onlyCreator public {
ISuperToken(superToken1).transfer(msg.sender, ISuperToken(superToken1).balanceOf(address(this)));
}
// utilities
function _isAccepted(ISuperToken _superToken) private view returns (bool) {
return address(_superToken) == address(superToken1);
}
function _isCFAv1(address _agreementClass) private view returns (bool) {
return ISuperAgreement(_agreementClass).agreementType() == _AGREEMENT_TYPE_CFAv1;
}
modifier onlyHost() {
require(msg.sender == address(host), "RedirectAll: support only one host");
_;
}
modifier onlyCreator() {
require(msg.sender == creator, "Creator: Only creator could call.");
_;
}
modifier onlyExpected(ISuperToken _superToken, address _agreementClass) {
require(_isAccepted(_superToken) , "Exchange: not accepted tokens");
require(_isCFAv1(_agreementClass), "Exchange: only CFAv1 supported");
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0;
import {
ISuperfluid,
ISuperToken,
ISuperApp,
ISuperAgreement,
SuperAppDefinitions
} from "https://github.com/superfluid-finance/protocol-monorepo/blob/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluid.sol";
import {
ISuperTokenFactory
}
from "https://github.com/superfluid-finance/protocol-monorepo/blob/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperTokenFactory.sol";
import {
IConstantFlowAgreementV1
} from "https://github.com/superfluid-finance/protocol-monorepo/blob/dev/packages/ethereum-contracts/contracts/interfaces/agreements/IConstantFlowAgreementV1.sol";
import { INativeSuperToken, NativeSuperTokenProxy } from "./NativeSuperToken.sol";
import { AsteroidStream } from "./AsteroidStream.sol";
contract FluidMinersFactory {
event NewAsteroid(address indexed asteroid, address indexed token, int96 maxFlow, uint256 supply);
ISuperfluid private _host; // host
ISuperToken private _daiToken; // accepted token
ISuperTokenFactory private _superTokenFactory;
constructor(
ISuperfluid host,
ISuperToken daiToken,
ISuperTokenFactory superTokenFactory
) {
_host = host;
_daiToken = daiToken;
_superTokenFactory = superTokenFactory;
}
function deployAsteroid(
uint256 _supply,
int96 _maxFlow,
string calldata _nameast,
string calldata _name,
string calldata _symbol
) external {
// Deploy the Custom Super Token proxy
INativeSuperToken newToken = INativeSuperToken(address(new NativeSuperTokenProxy()));
// Deploy the machine using the new token address
AsteroidStream stream = new AsteroidStream(_host, _daiToken, ISuperToken(address(newToken)), msg.sender, _maxFlow, _nameast);
// Set the proxy to use the Super Token logic managed by Superfluid Protocol Governance
_superTokenFactory.initializeCustomSuperToken(address(newToken));
// Set up the token and mint tokens
newToken.initialize(
_name,
_symbol,
_supply,
address(stream)
);
emit NewAsteroid(address(stream), address(newToken), _maxFlow, _supply);
}
}
// SPDX-License-Identifier: AGPLv3
pragma solidity >=0.7.0;
import {
ISuperToken
}
from "https://github.com/superfluid-finance/protocol-monorepo/blob/dev/packages/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { Proxy } from "@openzeppelin/contracts/proxy/Proxy.sol";
/**
* @title UUPS (Universal Upgradeable Proxy Standard) Shared Library
*/
library UUPSUtils {
/**
* @dev Implementation slot constant.
* Using https://eips.ethereum.org/EIPS/eip-1967 standard
* Storage slot 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
* (obtained as bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1)).
*/
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/// @dev Get implementation address.
function implementation() internal view returns (address impl) {
assembly { // solium-disable-line
impl := sload(_IMPLEMENTATION_SLOT)
}
}
/// @dev Set new implementation address.
function setImplementation(address codeAddress) internal {
assembly {
// solium-disable-line
sstore(
_IMPLEMENTATION_SLOT,
codeAddress
)
}
}
}
/**
* @dev UUPS (Universal Upgradeable Proxy Standard) Proxy
*
* NOTE:
* - Compliant with [Universal Upgradeable Proxy Standard](https://eips.ethereum.org/EIPS/eip-1822)
* - Compiiant with [Standard Proxy Storage Slots](https://eips.ethereum.org/EIPS/eip-1967)
* - Implements delegation of calls to other contracts, with proper forwarding of
* return values and bubbling of failures.
* - It defines a fallback function that delegates all calls to the implementation.
*/
contract UUPSProxy is Proxy {
/**
* @dev Proxy initialization function.
* This should only be called once and it is permission-less.
* @param initialAddress Initial logic contract code address to be used.
*/
function initializeProxy(address initialAddress) external {
require(initialAddress != address(0), "UUPSProxy: zero address");
require(UUPSUtils.implementation() == address(0), "UUPSProxy: already initialized");
UUPSUtils.setImplementation(initialAddress);
}
/// @dev Proxy._implementation implementation
function _implementation() internal virtual override view returns (address)
{
return UUPSUtils.implementation();
}
}
/**
* @dev Custom super token proxy base contract
*
* NOTE:
* - Because of how solidity is layouting its storages variables and custom
* super token inherits the Super Token standard implementation, so it is
* required that the custom token proxy would need to pad its implementation
* with reserved storages used by the Super Token implementation.
* - Refer to SETH.sol for an example how it is used.
*/
abstract contract CustomSuperTokenProxyBase is UUPSProxy {
// This is the hard-coded number of storage slots used by the super token
uint256[32] internal _storagePaddings;
}
/**
* @dev Native SuperToken custom token functions
*
* @author Superfluid
*/
interface INativeSuperTokenCustom {
function initialize(string calldata name, string calldata symbol, uint256 initialSupply, address recipient) external;
}
/**
* @dev Native SuperToken full interface
*
* @author Superfluid
*/
interface INativeSuperToken is INativeSuperTokenCustom, ISuperToken {
function initialize(string calldata name, string calldata symbol, uint256 initialSupply, address recipient) external override;
}
/**
* @dev Native SuperToken custom super token implementation
*
* NOTE:
* - This is a simple implementation where the supply is pre-minted.
*
* @author Superfluid
*/
contract NativeSuperTokenProxy is INativeSuperTokenCustom, CustomSuperTokenProxyBase {
function initialize(string calldata name, string calldata symbol, uint256 totalSupply, address recipient)
external override
{
ISuperToken(address(this)).initialize(
IERC20(address(0x0)), // no underlying/wrapped token
18, // shouldn't matter if there's no wrapped token
name,
symbol
);
ISuperToken(address(this)).selfMint(recipient, totalSupply, new bytes(0));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment