Skip to content

Commit

Permalink
Add Debt Issuance Module V2 (#125)
Browse files Browse the repository at this point in the history
* Add AaveDebtIssuanceModule contract

* Module designed to issue/redeem SetTokens which hold one or more
aTokens as components.

* Refactor integration tests

* Moved validation logic to IssuanceUtils library

* Fix compilation bugs

* Fix imports

* Refactor IssuanceUtils library contract

* Add new function which considers external positions
* Rename functions

* Refactor DebtIssuanceModuleV2 contract

* Override _resolveDebtPositions function
* Use new IssuanceUtils library contract
* Improve javadocs

* Add IssuanceUtils mock contract

* Refactor _setQuantity parameter in both library functions

* Add standard token with rounding error mock

* Returns a variable balance of value based on the error value set

* Will be helpful to mock the behaviour of aTokens

* Add tests for Debt issuance module V2 contract

* Skip ALM <> DIM integration tests

* Make validation logic statelesss

* Earlier we were assuming that burn takes place before and mint
takes place after the validation logic is called. But these assumptions
make the validation logic stateful, which is not ideal. By removing
those assumptions and passing in the required value from outside the
function, the validation logic becomes stateless, which allows it to be used at
multiple places flexibly.
* The validation functions now perform just the check. All required state to perform the check is passed from outside.

* Add external debt position to SetToken in tests

* Improve coverage: Add tests for issue/redeem quantity is 0

* Rename IssuanceUtils to IssuanceValidationUtils

* Update docs to specify redeem requires transferring in debt from caller

* Add more tests and improve existing ones

* Newly added tests are an EXACT copy of the tests for DebtIssuanceModule. Only difference is this SetToken contains tokenWithRoundingError instead of weth as a default position. This is to ensure the DebtIssuanceModuleV2 behaves exactly similar to DebtIssuanceModule when there is no rounding error present in it's constituent components.
* Existing tests are improved by introducing fees and by different
revert messages for different reverts

* Add more tests and optimize tests to complete faster

* Bump package to 0.0.52
  • Loading branch information
0xSachinK authored Aug 30, 2021
1 parent 70f926f commit f2d67d1
Show file tree
Hide file tree
Showing 11 changed files with 1,452 additions and 20 deletions.
180 changes: 180 additions & 0 deletions contracts/mocks/StandardTokenWithRoundingErrorMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*
Copyright 2020 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;

import "./StandardTokenMock.sol";
import "@openzeppelin/contracts/math/SignedSafeMath.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "@openzeppelin/contracts/utils/SafeCast.sol";

contract StandardTokenWithRoundingErrorMock {
using SignedSafeMath for int256;
using SafeCast for int256;
using SafeMath for uint256;
event Transfer(
address indexed from,
address indexed to,
uint256 value
);

event Approval(
address indexed owner,
address indexed spender,
uint256 value
);

uint256 constant public decimals = 18;
string public name;
string public symbol;
int256 public err;

mapping (address => uint256) public _balances;

mapping (address => mapping (address => uint256)) public _allowed;

uint256 public _totalSupply;


constructor(
address _initialAccount,
uint256 _initialBalance,
int256 _err,
string memory _name,
string memory _symbol,
uint8 _decimals
)
public

{
_balances[_initialAccount] = _initialBalance;
_totalSupply = _initialBalance;
name = _name;
symbol = _symbol;
err = _err;
}

/**
* @dev Returns balance of owner with the rounding error applied
* @param owner address whose balance is to be returned
*/
function balanceOf(address owner) external view returns (uint256) {
uint256 balance = _balances[owner];
if (err >= 0) {
return balance.add(err.toUint256());
} else {
uint256 absoluteError = err.mul(-1).toUint256();
if (balance >= absoluteError) {
return balance.sub(absoluteError);
} else {
return 0;
}
}
}

/**
* @dev Transfer tokens from one address to another
* @param _from address The address which you want to send tokens from
* @param _to address The address which you want to transfer to
* @param _value uint256 the amount of tokens to be transferred
*/
function transferFrom(address _from, address _to, uint256 _value) external returns (bool) {
require(_to != address(0), "to null");
require(_value <= _balances[_from], "value greater than from balance");
require(_value <= _allowed[_from][msg.sender], "value greater than allowed");

_balances[_from] = _balances[_from].sub(_value);
_balances[_to] = _balances[_to].add(_value);
_allowed[_from][msg.sender] = _allowed[_from][msg.sender].sub(_value);
emit Transfer(_from, _to, _value);
return true;
}

/**
* @dev Transfer tokens from one address to another
* @param _to The address to transfer to.
* @param _value The amount to be transferred.
*/
function transfer(address _to, uint256 _value) external returns (bool) {
require(_to != address(0), "to null");
require(_value <= _balances[msg.sender], "value greater than sender balance");

_balances[msg.sender] = _balances[msg.sender].sub(_value);
_balances[_to] = _balances[_to].add(_value);
emit Transfer(msg.sender, _to, _value);
return true;
}

function setError(int256 _err) external returns (bool) {
err = _err;
return true;
}

function totalSupply() external view returns (uint256) {
return _totalSupply;
}

function allowance(
address owner,
address spender
)
external
view
returns (uint256)
{
return _allowed[owner][spender];
}

function approve(address spender, uint256 value) external returns (bool) {
require(spender != address(0));

_allowed[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}

function increaseAllowance(
address spender,
uint256 addedValue
)
external
returns (bool)
{
require(spender != address(0));

_allowed[msg.sender][spender] = (
_allowed[msg.sender][spender].add(addedValue));
emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
return true;
}

function decreaseAllowance(
address spender,
uint256 subtractedValue
)
external
returns (bool)
{
require(spender != address(0));

_allowed[msg.sender][spender] = (
_allowed[msg.sender][spender].sub(subtractedValue));
emit Approval(msg.sender, spender, _allowed[msg.sender][spender]);
return true;
}
}
58 changes: 58 additions & 0 deletions contracts/mocks/protocol/lib/IssuanceValidationUtilsMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
Copyright 2021 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;

import { ISetToken } from "../../../interfaces/ISetToken.sol";
import { IssuanceValidationUtils } from "../../../protocol/lib/IssuanceValidationUtils.sol";

contract IssuanceValidationUtilsMock {
/* ============ External Functions ============ */

function testValidateCollateralizationPostTransferInPreHook(
ISetToken _setToken,
address _component,
uint256 _initialSetSupply,
uint256 _componentQuantity
)
external
view
{
IssuanceValidationUtils.validateCollateralizationPostTransferInPreHook(
_setToken,
_component,
_initialSetSupply,
_componentQuantity
);
}

function testValidateCollateralizationPostTransferOut(
ISetToken _setToken,
address _component,
uint256 _finalSetSupply
)
external
view
{
IssuanceValidationUtils.validateCollateralizationPostTransferOut(
_setToken,
_component,
_finalSetSupply
);
}
}
93 changes: 93 additions & 0 deletions contracts/protocol/lib/IssuanceValidationUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
Copyright 2021 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;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeCast } from "@openzeppelin/contracts/utils/SafeCast.sol";
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";

import { ISetToken } from "../../interfaces/ISetToken.sol";
import { PreciseUnitMath } from "../../lib/PreciseUnitMath.sol";

/**
* @title IssuanceValidationUtils
* @author Set Protocol
*
* A collection of utility functions to help during issuance/redemption of SetToken.
*/
library IssuanceValidationUtils {
using SafeMath for uint256;
using SafeCast for int256;
using PreciseUnitMath for uint256;

/**
* Validates component transfer IN to SetToken during issuance/redemption. Reverts if Set is undercollateralized post transfer.
* NOTE: Call this function immediately after transfer IN but before calling external hooks (if any).
*
* @param _setToken Instance of the SetToken being issued/redeemed
* @param _component Address of component being transferred in/out
* @param _initialSetSupply Initial SetToken supply before issuance/redemption
* @param _componentQuantity Amount of component transferred into SetToken
*/
function validateCollateralizationPostTransferInPreHook(
ISetToken _setToken,
address _component,
uint256 _initialSetSupply,
uint256 _componentQuantity
)
internal
view
{
uint256 newComponentBalance = IERC20(_component).balanceOf(address(_setToken));

uint256 defaultPositionUnit = _setToken.getDefaultPositionRealUnit(address(_component)).toUint256();

require(
// Use preciseMulCeil to increase the lower bound and maintain over-collateralization
newComponentBalance >= _initialSetSupply.preciseMulCeil(defaultPositionUnit).add(_componentQuantity),
"Invalid transfer in. Results in undercollateralization"
);
}

/**
* Validates component transfer OUT of SetToken during issuance/redemption. Reverts if Set is undercollateralized post transfer.
*
* @param _setToken Instance of the SetToken being issued/redeemed
* @param _component Address of component being transferred in/out
* @param _finalSetSupply Final SetToken supply after issuance/redemption
*/
function validateCollateralizationPostTransferOut(
ISetToken _setToken,
address _component,
uint256 _finalSetSupply
)
internal
view
{
uint256 newComponentBalance = IERC20(_component).balanceOf(address(_setToken));

uint256 defaultPositionUnit = _setToken.getDefaultPositionRealUnit(address(_component)).toUint256();

require(
// Use preciseMulCeil to increase lower bound and maintain over-collateralization
newComponentBalance >= _finalSetSupply.preciseMulCeil(defaultPositionUnit),
"Invalid transfer out. Results in undercollateralization"
);
}
}
7 changes: 5 additions & 2 deletions contracts/protocol/modules/DebtIssuanceModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {
address _to
)
external
virtual
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
Expand Down Expand Up @@ -151,8 +152,9 @@ contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {
}

/**
* Returns components from the SetToken, unwinds any external module component positions and burns
* the SetToken. If the token has a debt position all debt will be paid down first then equity positions
* Returns components from the SetToken, unwinds any external module component positions and burns the SetToken.
* If the token has debt positions, the module transfers in the required debt amounts from the caller and uses
* those funds to repay the debts on behalf of the SetToken. All debt will be paid down first then equity positions
* will be returned to the minting address. If specified, a fee will be charged on redeem.
*
* @param _setToken Instance of the SetToken to redeem
Expand All @@ -165,6 +167,7 @@ contract DebtIssuanceModule is ModuleBase, ReentrancyGuard {
address _to
)
external
virtual
nonReentrant
onlyValidAndInitializedSet(_setToken)
{
Expand Down
Loading

0 comments on commit f2d67d1

Please sign in to comment.