Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(emp): add trimExcess function to send excess tokens #1975

Merged
merged 16 commits into from
Sep 16, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ contract ExpiringMultiPartyCreator is ContractCreator, Testable, Lockable {
FixedPoint.Unsigned minSponsorTokens;
uint256 withdrawalLiveness;
uint256 liquidationLiveness;
address excessTokenBeneficiary;
}
// - Address of TokenFactory to pass into newly constructed ExpiringMultiParty contracts
address public tokenFactoryAddress;
Expand Down Expand Up @@ -93,6 +94,7 @@ contract ExpiringMultiPartyCreator is ContractCreator, Testable, Lockable {
require(bytes(params.syntheticSymbol).length != 0, "Missing synthetic symbol");
require(params.withdrawalLiveness != 0, "Withdrawal liveness cannot be 0");
require(params.liquidationLiveness != 0, "Liquidation liveness cannot be 0");
require(params.excessTokenBeneficiary != address(0), "Token Beneficiary cannot be 0x0");
require(params.expirationTimestamp > now, "Invalid expiration time");
_requireWhitelistedCollateral(params.collateralAddress);

Expand All @@ -117,5 +119,6 @@ contract ExpiringMultiPartyCreator is ContractCreator, Testable, Lockable {
constructorParams.minSponsorTokens = params.minSponsorTokens;
constructorParams.withdrawalLiveness = params.withdrawalLiveness;
constructorParams.liquidationLiveness = params.liquidationLiveness;
constructorParams.excessTokenBeneficiary = params.excessTokenBeneficiary;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ contract Liquidatable is PricelessPositionManager {
address finderAddress;
address tokenFactoryAddress;
address timerAddress;
address excessTokenBeneficiary;
bytes32 priceFeedIdentifier;
string syntheticName;
string syntheticSymbol;
Expand Down Expand Up @@ -165,7 +166,8 @@ contract Liquidatable is PricelessPositionManager {
params.syntheticSymbol,
params.tokenFactoryAddress,
params.minSponsorTokens,
params.timerAddress
params.timerAddress,
params.excessTokenBeneficiary
)
nonReentrant()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ contract PricelessPositionManager is FeePayer, AdministrateeInterface {
// The expiry price pulled from the DVM.
FixedPoint.Unsigned public expiryPrice;

// The excessTokenBeneficiary of any excess tokens added to the contract.
address public excessTokenBeneficiary;

/****************************************
* EVENTS *
****************************************/
Expand Down Expand Up @@ -149,6 +152,8 @@ contract PricelessPositionManager is FeePayer, AdministrateeInterface {
* @param _tokenFactoryAddress deployed UMA token factory to create the synthetic token.
* @param _minSponsorTokens minimum amount of collateral that must exist at any time in a position.
* @param _timerAddress Contract that stores the current time in a testing environment.
* @param _excessTokenBeneficiary Beneficiary to which all excess token balances that accrue in the contract can be
* sent.
* Must be set to 0x0 for production environments that use live time.
*/
constructor(
Expand All @@ -161,7 +166,8 @@ contract PricelessPositionManager is FeePayer, AdministrateeInterface {
string memory _syntheticSymbol,
address _tokenFactoryAddress,
FixedPoint.Unsigned memory _minSponsorTokens,
address _timerAddress
address _timerAddress,
address _excessTokenBeneficiary
) public FeePayer(_collateralAddress, _finderAddress, _timerAddress) nonReentrant() {
require(_expirationTimestamp > getCurrentTime(), "Invalid expiration in future");
require(_getIdentifierWhitelist().isIdentifierSupported(_priceIdentifier), "Unsupported price identifier");
Expand All @@ -172,6 +178,7 @@ contract PricelessPositionManager is FeePayer, AdministrateeInterface {
tokenCurrency = tf.createToken(_syntheticName, _syntheticSymbol, 18);
minSponsorTokens = _minSponsorTokens;
priceIdentifier = _priceIdentifier;
excessTokenBeneficiary = _excessTokenBeneficiary;
}

/****************************************
Expand Down Expand Up @@ -607,6 +614,25 @@ contract PricelessPositionManager is FeePayer, AdministrateeInterface {
return;
}

/**
* @notice Drains any excess balance of the provided ERC20 token to a pre-selected beneficiary.
* @dev This will drain down to the amount of tracked collateral and drain the full balance of any other token.
* @param token address of the ERC20 token whose excess balance should be drained.
*/
function trimExcess(IERC20 token) external fees() nonReentrant() returns (FixedPoint.Unsigned memory amount) {
FixedPoint.Unsigned memory balance = FixedPoint.Unsigned(token.balanceOf(address(this)));

if (address(token) == address(collateralCurrency)) {
// If it is the collateral currency, send only the amount that the contract is not tracking.
// Note: this could be due to rounding error or balance-changing tokens, like aTokens.
amount = balance.sub(_pfc());
} else {
// If it's not the collateral currency, send the entire balance.
amount = balance;
}
token.safeTransfer(excessTokenBeneficiary, amount.rawValue);
}

/**
* @notice Accessor method for a sponsor's collateral.
* @dev This is necessary because the struct returned by the positions() method shows
Expand Down
5 changes: 4 additions & 1 deletion packages/core/scripts/DeploySyntheticTokens.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const AddressWhitelist = artifacts.require("AddressWhitelist");
const ExpiringMultiPartyCreator = artifacts.require("ExpiringMultiPartyCreator");
const IdentifierWhitelist = artifacts.require("IdentifierWhitelist");
const Registry = artifacts.require("Registry");
const Store = artifacts.require("Store");

const collateral = {
"Kovan DAI": "0x08ae34860fbfe73e223596e65663683973c72dd3"
Expand Down Expand Up @@ -65,6 +66,7 @@ const actualDeploy = async inputCsv => {
const deployer = (await web3.eth.getAccounts())[0];
const expiringMultiPartyCreator = await ExpiringMultiPartyCreator.deployed();
const identifierWhitelist = await IdentifierWhitelist.deployed();
const store = await Store.deployed();

// Add EMP as a registered financial contract template factory.
const registry = await Registry.deployed();
Expand Down Expand Up @@ -99,7 +101,8 @@ const actualDeploy = async inputCsv => {
disputeBondPct: percentToFixedPoint(params.disputeBond),
sponsorDisputeRewardPct: percentToFixedPoint(params.sponsorDisputeReward),
disputerDisputeRewardPct: percentToFixedPoint(params.disputeReward),
minSponsorTokens: percentToFixedPoint(params.minSponsorTokens)
minSponsorTokens: percentToFixedPoint(params.minSponsorTokens),
excessTokenBeneficiary: store.address
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice. good default.

};
const address = await expiringMultiPartyCreator.createExpiringMultiParty.call(constructorParams);
await expiringMultiPartyCreator.createExpiringMultiParty(constructorParams);
Expand Down
7 changes: 6 additions & 1 deletion packages/core/scripts/local/DeployEMP.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const WETH9 = artifacts.require("WETH9");
const Timer = artifacts.require("Timer");
const TokenFactory = artifacts.require("TokenFactory");
const AddressWhitelist = artifacts.require("AddressWhitelist");
const Store = artifacts.require("Store");
const argv = require("minimist")(process.argv.slice(), { boolean: ["test"], string: ["identifier"] });

// Contracts we need to interact with.
Expand All @@ -44,6 +45,7 @@ let mockOracle;
let identifierWhitelist;
let collateralTokenWhitelist;
let expiringMultiPartyCreator;
let store;

const empCollateralTokenMap = {
COMPUSD: TestnetERC20,
Expand Down Expand Up @@ -88,6 +90,8 @@ const deployEMP = async callback => {
console.log("Whitelisted collateral currency");
}

store = await Store.deployed();

// Create a new EMP
const constructorParams = {
expirationTimestamp: "1601503200", // 09/30/2020 @ 10:00pm (UTC)
Expand All @@ -101,7 +105,8 @@ const deployEMP = async callback => {
disputerDisputeRewardPct: { rawValue: toWei("0.2") },
minSponsorTokens: { rawValue: toWei("100") },
liquidationLiveness: 7200,
withdrawalLiveness: 7200
withdrawalLiveness: 7200,
excessTokenBeneficiary: store.address
};

let _emp = await expiringMultiPartyCreator.createExpiringMultiParty.call(constructorParams, { from: deployer });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ contract("ExpiringMultiParty", function(accounts) {
sponsorDisputeRewardPct: { rawValue: toWei("0.1") },
disputerDisputeRewardPct: { rawValue: toWei("0.1") },
minSponsorTokens: { rawValue: toWei("1") },
timerAddress: timer.address
timerAddress: timer.address,
excessTokenBeneficiary: accounts[0]
};

identifierWhitelist = await IdentifierWhitelist.deployed();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { toWei, hexToUtf8, toBN } = web3.utils;
const { didContractThrow, MAX_UINT_VAL } = require("@uma/common");
const { didContractThrow, MAX_UINT_VAL, ZERO_ADDRESS } = require("@uma/common");
const truffleAssert = require("truffle-assertions");

// Tested Contract
Expand All @@ -12,8 +12,7 @@ const Registry = artifacts.require("Registry");
const ExpiringMultiParty = artifacts.require("ExpiringMultiParty");
const IdentifierWhitelist = artifacts.require("IdentifierWhitelist");
const AddressWhitelist = artifacts.require("AddressWhitelist");
const Timer = artifacts.require("Timer");
const Finder = artifacts.require("Finder");
const Store = artifacts.require("Store");

contract("ExpiringMultiPartyCreator", function(accounts) {
let contractCreator = accounts[0];
Expand All @@ -23,6 +22,7 @@ contract("ExpiringMultiPartyCreator", function(accounts) {
let expiringMultiPartyCreator;
let registry;
let collateralTokenWhitelist;
let store;

// Re-used variables
let constructorParams;
Expand All @@ -36,6 +36,8 @@ contract("ExpiringMultiPartyCreator", function(accounts) {
collateralTokenWhitelist = await AddressWhitelist.deployed();
await collateralTokenWhitelist.addToWhitelist(collateralToken.address, { from: contractCreator });

store = await Store.deployed();

constructorParams = {
expirationTimestamp: "1898918401", // 2030-03-05T05:20:01.000Z
collateralAddress: collateralToken.address,
Expand All @@ -48,7 +50,8 @@ contract("ExpiringMultiPartyCreator", function(accounts) {
disputerDisputeRewardPct: { rawValue: toWei("0.1") },
minSponsorTokens: { rawValue: toWei("1") },
liquidationLiveness: 7200,
withdrawalLiveness: 7200
withdrawalLiveness: 7200,
excessTokenBeneficiary: store.address
};

identifierWhitelist = await IdentifierWhitelist.deployed();
Expand Down Expand Up @@ -160,6 +163,18 @@ contract("ExpiringMultiPartyCreator", function(accounts) {
);
});

it("Beneficiary cannot be 0x0", async function() {
// Change only the beneficiary address.
constructorParams.excessTokenBeneficiary = ZERO_ADDRESS;
assert(
await didContractThrow(
expiringMultiPartyCreator.createExpiringMultiParty(constructorParams, {
from: contractCreator
})
)
);
});

it("Can create new instances of ExpiringMultiParty", async function() {
// Use `.call` to get the returned value from the function.
let functionReturnedAddress = await expiringMultiPartyCreator.createExpiringMultiParty.call(constructorParams, {
Expand Down
Loading