Skip to content

Commit

Permalink
feat(module): Notional Trade Module (#251)
Browse files Browse the repository at this point in the history
  • Loading branch information
ckoopmann committed May 23, 2022
1 parent 56cb46e commit 49bdf46
Show file tree
Hide file tree
Showing 28 changed files with 3,691 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .solhint.json
Expand Up @@ -2,6 +2,6 @@
"extends": "solhint:recommended",
"rules": {
"reason-string": ["warn", { "maxLength": 50 }],
"compiler-version": ["error", "0.6.10"]
"compiler-version": ["error", ">=0.6.10"]
}
}
1 change: 1 addition & 0 deletions .solhintignore
Expand Up @@ -2,3 +2,4 @@ node_modules
contracts/mocks
contracts/Migrations.sol
contracts/external
contracts/protocol/integration/wrap/notional
68 changes: 68 additions & 0 deletions contracts/interfaces/IWrappedFCash.sol
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @notice Different types of internal tokens
/// - UnderlyingToken: underlying asset for a cToken (except for Ether)
/// - cToken: Compound interest bearing token
/// - cETH: Special handling for cETH tokens
/// - Ether: the one and only
/// - NonMintable: tokens that do not have an underlying (therefore not cTokens)
enum TokenType {
UnderlyingToken,
cToken,
cETH,
Ether,
NonMintable
}

interface IWrappedfCash {
function initialize(uint16 currencyId, uint40 maturity) external;

/// @notice Mints wrapped fCash ERC20 tokens
function mintViaAsset(
uint256 depositAmountExternal,
uint88 fCashAmount,
address receiver,
uint32 minImpliedRate
) external;

function mintViaUnderlying(
uint256 depositAmountExternal,
uint88 fCashAmount,
address receiver,
uint32 minImpliedRate
) external;

function redeemToAsset(uint256 amount, address receiver, uint32 maxImpliedRate) external;
function redeemToUnderlying(uint256 amount, address receiver, uint32 maxImpliedRate) external;

/// @notice Returns the underlying fCash ID of the token
function getfCashId() external view returns (uint256);

/// @notice True if the fCash has matured, assets mature exactly on the block time
function hasMatured() external view returns (bool);

/// @notice Returns the components of the fCash idd
function getDecodedID() external view returns (uint16 currencyId, uint40 maturity);

/// @notice Returns the current market index for this fCash asset. If this returns
/// zero that means it is idiosyncratic and cannot be traded.
function getMarketIndex() external view returns (uint8);

/// @notice Returns the token and precision of the token that this token settles
/// to. For example, fUSDC will return the USDC token address and 1e6. The zero
/// address will represent ETH.
function getUnderlyingToken() external view returns (IERC20 underlyingToken, int256 underlyingPrecision);

/// @notice Returns the asset token which the fCash settles to. This will be an interest
/// bearing token like a cToken or aToken.
function getAssetToken() external view returns (IERC20 assetToken, int256 assetPrecision, TokenType tokenType);

function getToken(bool useUnderlying) external view returns (IERC20 token, bool isETH);
}


interface IWrappedfCashComplete is IWrappedfCash, IERC20 {}
9 changes: 9 additions & 0 deletions contracts/interfaces/IWrappedFCashFactory.sol
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.6.10;

interface IWrappedfCashFactory {
function deployWrapper(uint16 currencyId, uint40 maturity) external returns(address);
function computeAddress(uint16 currencyId, uint40 maturity) external view returns(address);
}


2 changes: 1 addition & 1 deletion contracts/interfaces/external/ICErc20.sol
Expand Up @@ -43,7 +43,7 @@ interface ICErc20 is IERC20 {

function exchangeRateStored() external view returns (uint256);

function underlying() external returns (address);
function underlying() external view returns (address);

/**
* Sender supplies assets into the market and receives cTokens in exchange
Expand Down
5 changes: 5 additions & 0 deletions contracts/interfaces/external/INotionalProxy.sol
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.11;
import { NotionalProxy } from "notional-solidity-sdk/interfaces/notional/NotionalProxy.sol";

interface INotionalProxy is NotionalProxy {}
50 changes: 50 additions & 0 deletions contracts/mocks/WrappedfCashFactoryMock.sol
@@ -0,0 +1,50 @@
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/

pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";

import { IWrappedfCashFactory } from "../interfaces/IWrappedFCashFactory.sol";
import { WrappedfCashMock } from "./WrappedfCashMock.sol";


// mock class using BasicToken
contract WrappedfCashFactoryMock is IWrappedfCashFactory {

mapping(uint16 => mapping(uint40 => address)) paramsToAddress;
bool private revertComputeAddress;

function registerWrapper(uint16 _currencyId, uint40 _maturity, address _fCashWrapper) external {
paramsToAddress[_currencyId][_maturity] = _fCashWrapper;
}

function deployWrapper(uint16 _currencyId, uint40 _maturity) external override returns(address) {
return computeAddress(_currencyId, _maturity);
}

function computeAddress(uint16 _currencyId, uint40 _maturity) public view override returns(address) {
require(!revertComputeAddress, "Test revertion ComputeAddress");
return paramsToAddress[_currencyId][_maturity];
}

function setRevertComputeAddress(bool _revertComputeAddress) external{
revertComputeAddress = _revertComputeAddress;
}


}
175 changes: 175 additions & 0 deletions contracts/mocks/WrappedfCashMock.sol
@@ -0,0 +1,175 @@
/*
Copyright 2022 Set Labs Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
SPDX-License-Identifier: Apache License, Version 2.0
*/

pragma solidity 0.6.10;
pragma experimental "ABIEncoderV2";

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { TokenType, IWrappedfCash } from "../interfaces/IWrappedFCash.sol";

// mock class using BasicToken
contract WrappedfCashMock is ERC20, IWrappedfCash {

uint256 private fCashId;
uint40 private maturity;
bool private matured;
uint16 private currencyId;
uint8 private marketIndex;
IERC20 private underlyingToken;
int256 private underlyingPrecision;
IERC20 private assetToken;
int256 private assetPrecision;
TokenType private tokenType;

IERC20 private weth;

bool private revertDecodedID;

uint256 public redeemTokenReturned;
uint256 public mintTokenSpent;

address internal constant ETH_ADDRESS = address(0);

constructor (IERC20 _assetToken, IERC20 _underlyingToken, IERC20 _weth) public ERC20("FCashMock", "FCM") {
assetToken = _assetToken;
underlyingToken = _underlyingToken;
weth = _weth;
}

function initialize(uint16 _currencyId, uint40 _maturity) external override {
currencyId = _currencyId;
maturity = _maturity;
}

/// @notice Mints wrapped fCash ERC20 tokens
function mintViaAsset(
uint256 depositAmountExternal,
uint88 fCashAmount,
address receiver,
uint32 /* minImpliedRate */
) external override{
uint256 assetTokenAmount = mintTokenSpent == 0 ? depositAmountExternal : mintTokenSpent;
require(assetToken.transferFrom(msg.sender, address(this), assetTokenAmount), "WrappedfCashMock: Transfer failed");
_mint(receiver, fCashAmount);
}

function mintViaUnderlying(
uint256 depositAmountExternal,
uint88 fCashAmount,
address receiver,
uint32 /* minImpliedRate */
) external override{
uint256 underlyingTokenAmount = mintTokenSpent == 0 ? depositAmountExternal : mintTokenSpent;
bool transferSuccess;
if(address(underlyingToken) == ETH_ADDRESS) {
transferSuccess = weth.transferFrom(msg.sender, address(this), underlyingTokenAmount);
} else {
transferSuccess = underlyingToken.transferFrom(msg.sender, address(this), underlyingTokenAmount);
}
require(transferSuccess, "WrappedfCashMock: Transfer failed");
_mint(receiver, fCashAmount);
}


function redeemToAsset(
uint256 amount,
address receiver,
uint32 /* maxImpliedRate */
) external override {
_burn(msg.sender, amount);
uint256 assetTokenAmount = redeemTokenReturned == 0 ? amount : redeemTokenReturned;
require(assetToken.transfer(receiver, assetTokenAmount), "WrappedfCashMock: Transfer failed");
}

function redeemToUnderlying(
uint256 amount,
address receiver,
uint32 /* maxImpliedRate */
) external override {
_burn(msg.sender, amount);
uint256 underlyingTokenAmount = redeemTokenReturned == 0 ? amount : redeemTokenReturned;
if(address(underlyingToken) == ETH_ADDRESS) {
weth.transfer(receiver, underlyingTokenAmount);
} else {
underlyingToken.transfer(receiver, underlyingTokenAmount);
}
}

/// @notice Returns the underlying fCash ID of the token
function getfCashId() external override view returns (uint256) {
return fCashId;
}

/// @notice True if the fCash has matured, assets mature exactly on the block time
function hasMatured() external override view returns (bool) {
return matured;
}

/// @notice Returns the components of the fCash idd
function getDecodedID() external override view returns (uint16, uint40) {
require(!revertDecodedID, "Test revertion DecodedID");
return (currencyId, maturity);
}

/// @notice Returns the current market index for this fCash asset. If this returns
/// zero that means it is idiosyncratic and cannot be traded.
function getMarketIndex() external override view returns (uint8) {
return marketIndex;
}

/// @notice Returns the token and precision of the token that this token settles
/// to. For example, fUSDC will return the USDC token address and 1e6. The zero
/// address will represent ETH.
function getUnderlyingToken() public override view returns (IERC20, int256) {
return (underlyingToken, underlyingPrecision);
}

/// @notice Returns the asset token which the fCash settles to. This will be an interest
/// bearing token like a cToken or aToken.
function getAssetToken() public override view returns (IERC20, int256, TokenType) {
return (assetToken, assetPrecision, tokenType);
}

function setMatured(bool _matured) external{
matured = _matured;
}

function setRedeemTokenReturned(uint256 _redeemTokenReturned) external{
redeemTokenReturned = _redeemTokenReturned;
}

function setMintTokenSpent(uint256 _mintTokenSpent) external{
mintTokenSpent = _mintTokenSpent;
}

function setRevertDecodedID(bool _revertDecodedID) external{
revertDecodedID = _revertDecodedID;
}

function getToken(bool useUnderlying) public view override returns (IERC20 token, bool isETH) {
if (useUnderlying) {
(token, /* */) = getUnderlyingToken();
} else {
(token, /* */, /* */) = getAssetToken();
}
isETH = address(token) == ETH_ADDRESS;
}


}
10 changes: 10 additions & 0 deletions contracts/protocol/integration/wrap/notional/WrappedfCash.sol
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
import { wfCashERC4626 } from "wrapped-fcash/contracts/wfCashERC4626.sol";
import { INotionalV2 } from "wrapped-fcash/interfaces/notional/INotionalV2.sol";
import { IWETH9 } from "wrapped-fcash/interfaces/IWETH9.sol";

contract WrappedfCash is wfCashERC4626 {
constructor(INotionalV2 _notionalProxy, IWETH9 _weth) wfCashERC4626(_notionalProxy, _weth){
}
}
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
import { WrappedfCashFactory as WrappedfCashFactoryBase } from "wrapped-fcash/contracts/proxy/WrappedfCashFactory.sol";

contract WrappedfCashFactory is WrappedfCashFactoryBase {
constructor(address _beacon) WrappedfCashFactoryBase(_beacon){
}
}
7 changes: 7 additions & 0 deletions contracts/protocol/integration/wrap/notional/nBeaconProxy.sol
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;
import { nBeaconProxy as nBeaconProxyBase } from "wrapped-fcash/contracts/proxy/nBeaconProxy.sol";

contract nBeaconProxy is nBeaconProxyBase {
constructor(address beacon, bytes memory data) payable nBeaconProxyBase(beacon, data) { }
}
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.11;

import "openzeppelin-contracts-V4/proxy/beacon/UpgradeableBeacon.sol";

/// @dev Re-exporting to make available to brownie
/// UpgradeableBeacon is Ownable, default owner is the deployer
contract nUpgradeableBeacon is UpgradeableBeacon {
constructor(address implementation_) UpgradeableBeacon(implementation_) {}
}

0 comments on commit 49bdf46

Please sign in to comment.