Skip to content
This repository was archived by the owner on Jan 18, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion contracts/core/modules/ExchangeIssuanceModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ contract ExchangeIssuanceModule is
_exchangeIssuanceParams.sendTokens
);

// Redeem Set into the vault, attributed to this contract
// Redeem Set into the vault, attributing components to this contract
coreInstance.redeemModule(
msg.sender,
address(this),
Expand All @@ -178,6 +178,9 @@ contract ExchangeIssuanceModule is
_exchangeIssuanceParams.receiveTokenAmounts
);

// Withdraw any remaining non-exchanged components to the user
withdrawRemainingComponentsToUser(_exchangeIssuanceParams.setAddress);

emit LogExchangeRedeem(
_exchangeIssuanceParams.setAddress,
msg.sender,
Expand Down Expand Up @@ -277,4 +280,33 @@ contract ExchangeIssuanceModule is
);
}
}

/**
* Withdraws any remaining un-exchanged components from the Vault in the posession of this contract
* to the caller
*
* @param _setAddress Address of the Base Set
*/
function withdrawRemainingComponentsToUser(
address _setAddress
)
private
{
address[] memory baseSetComponents = ISetToken(_setAddress).getComponents();
uint256[] memory baseSetWithdrawQuantities = new uint256[](baseSetComponents.length);
for (uint256 i = 0; i < baseSetComponents.length; i++) {
uint256 withdrawQuantity = vaultInstance.getOwnerBalance(baseSetComponents[i], address(this));
if (withdrawQuantity > 0) {
baseSetWithdrawQuantities[i] = withdrawQuantity;
}
}

// Return the unexchanged components to the user
coreInstance.batchWithdrawModule(
address(this),
msg.sender,
baseSetComponents,
baseSetWithdrawQuantities
);
}
}
43 changes: 34 additions & 9 deletions contracts/core/modules/RebalancingSetExchangeIssuanceModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import { IRebalancingSetToken } from "../interfaces/IRebalancingSetToken.sol";
import { ISetToken } from "../interfaces/ISetToken.sol";
import { ITransferProxy } from "../interfaces/ITransferProxy.sol";
import { IWETH } from "../../lib/IWETH.sol";
import { ModuleCoreState } from "./lib/ModuleCoreState.sol";


/**
Expand All @@ -39,16 +40,13 @@ import { IWETH } from "../../lib/IWETH.sol";
* issue a rebalancing Set
*/
contract RebalancingSetExchangeIssuanceModule is
ModuleCoreState,
ReentrancyGuard
{
using SafeMath for uint256;

/* ============ State Variables ============ */

// Address and instance of Core contract
address public core;
ICore private coreInstance;

// Address and instance of Transfer Proxy contract
address public transferProxy;

Expand Down Expand Up @@ -83,19 +81,21 @@ contract RebalancingSetExchangeIssuanceModule is
* @param _transferProxy The address of the TransferProxy
* @param _exchangeIssuanceModule The address of ExchangeIssuanceModule
* @param _wrappedEther The address of wrapped ether
* @param _vault The address of Vault
*/
constructor(
address _core,
address _transferProxy,
address _exchangeIssuanceModule,
address _wrappedEther
address _wrappedEther,
address _vault
)
public
ModuleCoreState(
_core,
_vault
)
{
// Commit the address and instance of Core to state variables
core = _core;
coreInstance = ICore(_core);

// Commit the address and instance of Transfer Proxy to state variables
transferProxy = _transferProxy;

Expand Down Expand Up @@ -249,6 +249,9 @@ contract RebalancingSetExchangeIssuanceModule is
// Send eth to user
msg.sender.transfer(wethBalance);

// Non-exchanged components are returned to the user
returnExcessBaseSetComponents(_exchangeIssuanceParams.setAddress);

emit LogPayableExchangeRedeem(
_rebalancingSetAddress,
msg.sender,
Expand Down Expand Up @@ -367,4 +370,26 @@ contract RebalancingSetExchangeIssuanceModule is
"RebalancingSetExchangeIssuanceModule.validateRedeemInputs: Base Set quantities must match"
);
}
/**
* Withdraw any non-exchanged components to the user
*
* @param _setAddress Address of the Base Set
*/
function returnExcessBaseSetComponents(
address _setAddress
)
private
{
address[] memory baseSetComponents = ISetToken(_setAddress).getComponents();
for (uint256 i = 0; i < baseSetComponents.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we do individual transfers here when we batch them in the ExchangeIssuanceModule contract?

Copy link
Contributor

Choose a reason for hiding this comment

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

Nvm we don't have a batch transfer functionality

uint256 withdrawQuantity = ERC20Wrapper.balanceOf(baseSetComponents[i], address(this));
if (withdrawQuantity > 0) {
ERC20Wrapper.transfer(
baseSetComponents[i],
msg.sender,
withdrawQuantity
);
}
}
}
}
2 changes: 2 additions & 0 deletions deployments/stages/3_modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ export class ModulesStage implements DeploymentStageInterface {
const transferProxyAddress = await getContractAddress(TransferProxy.contractName);
const exchangeIssuanceAddress = await getContractAddress(ExchangeIssuanceModule.contractName);
const erc20WrapperAddress = await getContractAddress(ERC20Wrapper.contractName);
const vaultAddress = await getContractAddress(Vault.contractName);
const wethAddress = await findDependency(DEPENDENCY.WETH);

const originalByteCode = RebalancingSetExchangeIssuanceModule.bytecode;
Expand All @@ -158,6 +159,7 @@ export class ModulesStage implements DeploymentStageInterface {
transferProxyAddress,
exchangeIssuanceAddress,
wethAddress,
vaultAddress,
],
}).encodeABI();

Expand Down
24 changes: 20 additions & 4 deletions test/contracts/core/modules/exchangeIssuanceModule.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,13 @@ contract('ExchangeIssuanceModule', accounts => {
let subjectExchangeIssuanceParams: ExchangeIssuanceParams;
let subjectExchangeOrdersData: Bytes;

let setComponentUnit: BigNumber;
let naturalUnit: BigNumber;
let setToken: SetTokenContract;
let receiveToken: StandardTokenMockContract;

let nonExchangedComponent: StandardTokenMockContract;

let totalReceiveToken: BigNumber;

let exchangeRedeemSetAddress: Address;
Expand Down Expand Up @@ -577,10 +580,11 @@ contract('ExchangeIssuanceModule', accounts => {

const firstComponent = erc20Wrapper.kyberReserveToken(SetTestUtils.KYBER_RESERVE_SOURCE_TOKEN_ADDRESS);
const secondComponent = await erc20Wrapper.deployTokenAsync(contractDeployer);
nonExchangedComponent = await erc20Wrapper.deployTokenAsync(contractDeployer);
receiveToken = erc20Wrapper.kyberReserveToken(SetTestUtils.KYBER_RESERVE_DESTINATION_TOKEN_ADDRESS);

const componentTokens = [firstComponent, secondComponent];
const setComponentUnit = ether(4);
const componentTokens = [firstComponent, secondComponent, nonExchangedComponent];
setComponentUnit = ether(4);
const componentAddresses = componentTokens.map(token => token.address);
const componentUnits = componentTokens.map(token => setComponentUnit);
naturalUnit = ether(2);
Expand All @@ -605,7 +609,7 @@ contract('ExchangeIssuanceModule', accounts => {

exchangeRedeemSendTokens = exchangeRedeemSendTokens || [firstComponent.address, secondComponent.address];
exchangeRedeemSendTokenAmounts =
exchangeRedeemSendTokenAmounts || _.map(componentUnits, unit => unit
exchangeRedeemSendTokenAmounts || _.map(componentUnits.slice(0, 2), unit => unit
.mul(exchangeRedeemQuantity)
.div(naturalUnit)
);
Expand Down Expand Up @@ -684,7 +688,7 @@ contract('ExchangeIssuanceModule', accounts => {
);

await erc20Wrapper.approveTransfersAsync(
[firstComponent, secondComponent],
[firstComponent, secondComponent, nonExchangedComponent],
transferProxy.address,
contractDeployer
);
Expand Down Expand Up @@ -736,6 +740,18 @@ contract('ExchangeIssuanceModule', accounts => {
await expect(newBalance).to.be.bignumber.equal(expectedNewBalance);
});

it('returns the correct quantity of non-exchanged tokens', async () => {
const existingBalance = await nonExchangedComponent.balanceOf.callAsync(exchangeIssuanceCaller);

await subject();

const incrementQuantity = setComponentUnit.mul(exchangeRedeemQuantity).div(naturalUnit);
const expectedNewBalance = existingBalance.add(incrementQuantity);
const newBalance = await nonExchangedComponent.balanceOf.callAsync(exchangeIssuanceCaller);

await expect(newBalance).to.be.bignumber.equal(expectedNewBalance);
});

it('emits correct LogExchangeRedeem event', async () => {
const txHash = await subject();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ contract('RebalancingSetExchangeIssuanceModule', accounts => {
transferProxy.address,
exchangeIssuanceModule.address,
weth.address,
vault.address,
);
await coreWrapper.addModuleAsync(core, rebalancingSetExchangeIssuanceModule.address);

Expand Down Expand Up @@ -378,8 +379,14 @@ contract('RebalancingSetExchangeIssuanceModule', accounts => {
let subjectCaller: Address;

let customExchangeRedeemQuantity: BigNumber;
let customExchangeRedeemSendTokenAmounts: BigNumber[];
let customBaseSetComponent: StandardTokenMockContract;
let customComponentAddresses: Address[];
let customComponentUnits: BigNumber[];

let baseSetComponent: StandardTokenMockContract;
let nonExchangedWethQuantity: BigNumber;

let baseSetToken: SetTokenContract;
let baseSetNaturalUnit: BigNumber;
let rebalancingSetToken: RebalancingSetTokenContract;
Expand All @@ -401,11 +408,11 @@ contract('RebalancingSetExchangeIssuanceModule', accounts => {
etherQuantityToReceive = ether(2);

// Create component token
baseSetComponent = await erc20Wrapper.deployTokenAsync(tokenPurchaser);
baseSetComponent = customBaseSetComponent || await erc20Wrapper.deployTokenAsync(tokenPurchaser);

// Create the Set (1 component)
const componentAddresses = [baseSetComponent.address];
const componentUnits = [new BigNumber(10 ** 10)];
// Create the Set (2 component where one is WETH)
const componentAddresses = customComponentAddresses || [baseSetComponent.address, weth.address];
const componentUnits = customComponentUnits || [new BigNumber(10 ** 10), new BigNumber(10 ** 10)];
baseSetNaturalUnit = new BigNumber(10 ** 9);
baseSetToken = await coreWrapper.createSetTokenAsync(
core,
Expand All @@ -430,10 +437,9 @@ contract('RebalancingSetExchangeIssuanceModule', accounts => {
exchangeRedeemSetAddress = baseSetToken.address;
exchangeRedeemQuantity = customExchangeRedeemQuantity || new BigNumber(10 ** 10);
exchangeRedeemSendTokenExchangeIds = [SetUtils.EXCHANGES.ZERO_EX];
exchangeRedeemSendTokens = componentAddresses;
exchangeRedeemSendTokenAmounts = componentUnits.map(
unit => unit.mul(exchangeRedeemQuantity).div(baseSetNaturalUnit)
);
exchangeRedeemSendTokens = [componentAddresses[0]];
exchangeRedeemSendTokenAmounts = customExchangeRedeemSendTokenAmounts ||
[componentUnits[0].mul(exchangeRedeemQuantity).div(baseSetNaturalUnit)];
exchangeRedeemReceiveTokens = [weth.address];
exchangeRedeemReceiveTokenAmounts = [etherQuantityToReceive];

Expand Down Expand Up @@ -486,6 +492,20 @@ contract('RebalancingSetExchangeIssuanceModule', accounts => {
tokenPurchaser
);

nonExchangedWethQuantity = componentUnits[1].mul(exchangeRedeemQuantity).div(baseSetNaturalUnit);

// Approve Weth to the transferProxy
await weth.approve.sendTransactionAsync(
transferProxy.address,
nonExchangedWethQuantity,
{ from: tokenPurchaser, gas: DEFAULT_GAS }
);

// Generate wrapped Ether for the caller
await weth.deposit.sendTransactionAsync(
{ from: tokenPurchaser, value: nonExchangedWethQuantity.toString(), gas: DEFAULT_GAS }
);

// Issue the Base Set to the vault
await core.issueInVault.sendTransactionAsync(
baseSetToken.address,
Expand Down Expand Up @@ -540,7 +560,10 @@ contract('RebalancingSetExchangeIssuanceModule', accounts => {
const txHash = await subject();
const totalGasInEth = await getGasUsageInEth(txHash);

const expectedEthBalance = previousEthBalance.add(etherQuantityToReceive).sub(totalGasInEth);
const expectedEthBalance = previousEthBalance
.add(etherQuantityToReceive)
.add(nonExchangedWethQuantity)
.sub(totalGasInEth);
const currentEthBalance = await web3.eth.getBalance(subjectCaller);

expect(currentEthBalance).to.bignumber.equal(expectedEthBalance);
Expand Down Expand Up @@ -570,6 +593,74 @@ contract('RebalancingSetExchangeIssuanceModule', accounts => {
await SetTestUtils.assertLogEquivalence(formattedLogs, expectedLogs);
});

describe('when the Set has a component that has not been exchanged', async () => {
let nonExchangedNonWethComponent: StandardTokenMockContract;

before(async () => {
nonExchangedNonWethComponent = await erc20Wrapper.deployTokenAsync(tokenPurchaser);

customBaseSetComponent = await erc20Wrapper.deployTokenAsync(tokenPurchaser);
customComponentAddresses = [
customBaseSetComponent.address,
weth.address,
nonExchangedNonWethComponent.address,
];
customComponentUnits = [new BigNumber(10 ** 10), new BigNumber(10 ** 10), new BigNumber(10 ** 10)];

await erc20Wrapper.approveTransfersAsync(
[nonExchangedNonWethComponent],
transferProxy.address,
tokenPurchaser
);
});

after(async () => {
customBaseSetComponent = undefined;
customComponentAddresses = undefined;
customComponentUnits = undefined;
});

it('should send the extra asset to the caller', async () => {
const previousReturnedAssetBalance = await nonExchangedNonWethComponent.balanceOf.callAsync(subjectCaller);
const expectedReturnedAssetBalance = previousReturnedAssetBalance.add(
customComponentUnits[2].mul(exchangeRedeemQuantity).div(baseSetNaturalUnit)
);

await subject();

const currentReturnedAssetBalance = await nonExchangedNonWethComponent.balanceOf.callAsync(subjectCaller);
expect(expectedReturnedAssetBalance).to.bignumber.equal(currentReturnedAssetBalance);
});
});

describe('when the quantity of send token is less than the components redeemed', async () => {
let halfBaseComponentQuantity: BigNumber;

before(async () => {
const componentUnit = new BigNumber(10 ** 10);
const naturalUnit = new BigNumber(10 ** 9);
const redeemQuantity = new BigNumber(10 ** 10);

halfBaseComponentQuantity = componentUnit.mul(redeemQuantity).div(naturalUnit).div(2);

customExchangeRedeemSendTokenAmounts = [halfBaseComponentQuantity];
});

after(async () => {
customExchangeRedeemSendTokenAmounts = undefined;
});

it('should send the unsold components to the caller', async () => {
const previousReturnedAssetBalance = await baseSetComponent.balanceOf.callAsync(subjectCaller);
const expectedReturnedAssetBalance = previousReturnedAssetBalance.add(halfBaseComponentQuantity);

await subject();

const currentReturnedAssetBalance = await baseSetComponent.balanceOf.callAsync(subjectCaller);
expect(expectedReturnedAssetBalance).to.bignumber.equal(currentReturnedAssetBalance);
});
});

describe('when the receive tokens length is greater than 1', async () => {
beforeEach(async () => {
subjectExchangeIssuanceParams.receiveTokens = [weth.address, weth.address];
Expand Down
Loading