Skip to content
Closed
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
73 changes: 60 additions & 13 deletions contracts/modules/TransferManager/LockupVolumeRestrictionTM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ contract LockupVolumeRestrictionTM is ITransferManager {
uint[] _releaseFrequenciesSeconds,
uint[] _startTimes,
uint[] _totalAmounts
) external withPerm(ADMIN) {
) external withPerm(ADMIN) {
require(
_userAddresses.length == _lockUpPeriodsSeconds.length &&
_userAddresses.length == _releaseFrequenciesSeconds.length &&
Expand Down Expand Up @@ -168,6 +168,18 @@ contract LockupVolumeRestrictionTM is ITransferManager {
userLockUps.length--;
}

/**
* @notice Use to remove the lockup for multiple users
* @param _userAddresses Array of addresses of the user whose tokens are locked up
* @param _lockUpIndexes Array of indexes of the LockUp to remove for the given userAddress
*/
function removeLockUpMulti(address[] _userAddresses, uint[] _lockUpIndexes) external withPerm(ADMIN) {
require(_userAddresses.length == _lockUpIndexes.length, "Array length mismatch");
for (uint i = 0; i < _userAddresses.length; i++) {
removeLockUp(_userAddresses[i], _lockUpIndexes[i]);
}
}

/**
* @notice Lets the admin modify a volume restriction lockup for a given address.
* @param _userAddress Address of the user whose tokens should be locked up
Expand All @@ -184,9 +196,9 @@ contract LockupVolumeRestrictionTM is ITransferManager {
uint _releaseFrequencySeconds,
uint _startTime,
uint _totalAmount
) public withPerm(ADMIN) {
) public withPerm(ADMIN) {
require(_lockUpIndex < lockUps[_userAddress].length, "Array out of bounds exception");

require(_totalAmount >= lockUps[_userAddress][_lockUpIndex].alreadyWithdrawn, "Total amount should be >= already withdrawn amount");
uint256 startTime = _startTime;
// if a startTime of 0 is passed in, then start now.
if (startTime == 0) {
Expand Down Expand Up @@ -214,6 +226,44 @@ contract LockupVolumeRestrictionTM is ITransferManager {
);
}

/**
* @notice Lets the admin modify a volume restriction lockup for a multiple address.
* @param _userAddresses Array of address of the user whose tokens should be locked up
* @param _lockUpIndexes Array of indexes of the LockUp to edit for the given userAddress
* @param _lockUpPeriodsSeconds Array of unix timestamp for the list of lockups (seconds).
* @param _releaseFrequenciesSeconds How often to release a tranche of tokens (seconds)
* @param _startTimes Array of the start time of the lockups (seconds)
* @param _totalAmounts Array of total amount of locked up tokens for list of lockups.
*/
function modifyLockUpMulti(
address[] _userAddresses,
uint[] _lockUpIndexes,
uint[] _lockUpPeriodsSeconds,
uint[] _releaseFrequenciesSeconds,
uint[] _startTimes,
uint[] _totalAmounts
) public withPerm(ADMIN) {
require(
_userAddresses.length == _lockUpPeriodsSeconds.length &&
_userAddresses.length == _releaseFrequenciesSeconds.length &&
_userAddresses.length == _startTimes.length &&
_userAddresses.length == _totalAmounts.length &&
_userAddresses.length == _lockUpIndexes.length,
"Input array length mismatch"
);
for (uint i = 0; i < _userAddresses.length; i++) {
modifyLockUp(
_userAddresses[i],
_lockUpIndexes[i],
_lockUpPeriodsSeconds[i],
_releaseFrequenciesSeconds[i],
_startTimes[i],
_totalAmounts[i]
);
}

}

/**
* @notice Get the length of the lockups array for a specific user address
* @param _userAddress Address of the user whose tokens should be locked up
Expand All @@ -227,16 +277,13 @@ contract LockupVolumeRestrictionTM is ITransferManager {
* @param _userAddress Address of the user whose tokens should be locked up
* @param _lockUpIndex The index of the LockUp to edit for the given userAddress
*/
function getLockUp(
address _userAddress,
uint _lockUpIndex)
public view returns (
function getLockUp( address _userAddress, uint _lockUpIndex) public view returns (
uint lockUpPeriodSeconds,
uint releaseFrequencySeconds,
uint startTime,
uint totalAmount,
uint alreadyWithdrawn
) {
) {
require(_lockUpIndex < lockUps[_userAddress].length, "Array out of bounds exception");
LockUp storage userLockUp = lockUps[_userAddress][_lockUpIndex];
return (
Expand Down Expand Up @@ -315,15 +362,15 @@ contract LockupVolumeRestrictionTM is ITransferManager {
aLockUp = userLockUps[i];

// tokenSums[0] is allowed sum
if (allowedAmountPerLockup[i] >= tokenSums[0]) {
aLockUp.alreadyWithdrawn = aLockUp.alreadyWithdrawn.add(tokenSums[0]);
// we withdrew the entire tokenSums[0] from the lockup. We are done.
if (allowedAmountPerLockup[i] >= amount) {
aLockUp.alreadyWithdrawn = aLockUp.alreadyWithdrawn.add(amount);
// we withdrew the entire amount from the lockup. We are done.
break;
} else {
// we have to split the tokenSums[0] across mutiple lockUps
// we have to split the amount across mutiple lockUps
aLockUp.alreadyWithdrawn = aLockUp.alreadyWithdrawn.add(allowedAmountPerLockup[i]);
// subtract the amount withdrawn from this lockup
tokenSums[0] = tokenSums[0].sub(allowedAmountPerLockup[i]);
amount = amount.sub(allowedAmountPerLockup[i]);
}

}
Expand Down
137 changes: 120 additions & 17 deletions test/w_lockup_volume_restriction_transfer_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => {
STFactory: ${I_STFactory.address}
GeneralTransferManagerFactory: ${I_GeneralTransferManagerFactory.address}

LockupVolumeRestrictionTransferManagerFactory:
LockupVolumeRestrictionTransferManagerFactory:
${I_VolumeRestrictionTransferManagerFactory.address}
-----------------------------------------------------------------------------
`);
Expand Down Expand Up @@ -346,14 +346,14 @@ contract('LockupVolumeRestrictionTransferManager', accounts => {

// balance should be 9000000000000000000 here (9 eth)
let balance = await I_SecurityToken.balanceOf(account_investor2)

// create a lockup for their entire balance
// over 16 seconds total, with 4 periods of 4 seconds each.
// this will generate an exception because 9000000000000000000 / 4 = 2250000000000000000 but the token granularity is 1000000000000000000
await catchRevert(
I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 16, 4, 0, balance, { from: token_owner })
);

});

it("Should prevent the transfer of tokens in a lockup", async() => {
Expand Down Expand Up @@ -571,8 +571,111 @@ contract('LockupVolumeRestrictionTransferManager', accounts => {

});

it("Should be possible to edit multiple lockups at once", async() => {

let balancesBefore = {}

// should be 10000000000000000000
balancesBefore[account_investor2] = await I_SecurityToken.balanceOf(account_investor2)

// should be 5000000000000000000
balancesBefore[account_investor3] = await I_SecurityToken.balanceOf(account_investor3)

let lockUpCountsBefore = {}

// get lockups for acct 2
lockUpCountsBefore[account_investor2] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2);
assert.equal(lockUpCountsBefore[account_investor2], 2)

// get lockups for acct 3
lockUpCountsBefore[account_investor3] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor3);
assert.equal(lockUpCountsBefore[account_investor3], 1)

// create lockups for their entire balances
await I_VolumeRestrictionTransferManager.modifyLockUpMulti(
[account_investor2, account_investor3],
[1, 0], // modify the newest restrictions for both accts
[20, 20],
[4, 4],
[0, 0],
[balancesBefore[account_investor2], balancesBefore[account_investor3]],
{ from: token_owner }
);

await catchRevert(
I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 })
);

await catchRevert(
I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor3 })
);

let balancesAfter = {}
balancesAfter[account_investor2] = await I_SecurityToken.balanceOf(account_investor2)
assert.equal(balancesBefore[account_investor2].toString(), balancesAfter[account_investor2].toString())

balancesAfter[account_investor3] = await I_SecurityToken.balanceOf(account_investor3)
assert.equal(balancesBefore[account_investor3].toString(), balancesAfter[account_investor3].toString())

let lockUpCountsAfter = {}

// get lockups for acct 2
lockUpCountsAfter[account_investor2] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2);
assert.equal(lockUpCountsAfter[account_investor2], 2);

// get lockups for acct 3
lockUpCountsAfter[account_investor3] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor3);
assert.equal(lockUpCountsAfter[account_investor3], 1);

// wait 4 seconds
await increaseTime(4000);

// try transfers again
await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('2', 'ether'), { from: account_investor2 });
await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('1', 'ether'), { from: account_investor3 });


balancesAfter[account_investor2] = await I_SecurityToken.balanceOf(account_investor2)
assert.equal(balancesBefore[account_investor2].sub(web3.utils.toWei('2', 'ether')).toString(), balancesAfter[account_investor2].toString())

balancesAfter[account_investor3] = await I_SecurityToken.balanceOf(account_investor3)
assert.equal(balancesBefore[account_investor3].sub(web3.utils.toWei('1', 'ether')).toString(), balancesAfter[account_investor3].toString())

});

it("Should be possible to remove multiple lockups at once", async() => {
let lockUpCountsBefore = {}

// get lockups for acct 2
lockUpCountsBefore[account_investor2] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2);
assert.equal(lockUpCountsBefore[account_investor2], 2)


// get lockups for acct 3
lockUpCountsBefore[account_investor3] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor3);
assert.equal(lockUpCountsBefore[account_investor3], 1)

// create lockups for their entire balances
await I_VolumeRestrictionTransferManager.removeLockUpMulti(
[account_investor2, account_investor3],
[1, 0], // remove the newest restrictions for both accts
{ from: token_owner }
);


let lockUpCountsAfter = {}

// get lockups for acct 2
lockUpCountsAfter[account_investor2] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2);
assert.equal(lockUpCountsAfter[account_investor2], 1);

// get lockups for acct 3
lockUpCountsAfter[account_investor3] = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor3);
assert.equal(lockUpCountsAfter[account_investor3], 0);
});

it("Should revert if the parameters are bad when creating multiple lockups", async() => {

await catchRevert(
// pass in the wrong number of params. txn should revert
I_VolumeRestrictionTransferManager.addLockUpMulti(
Expand All @@ -590,18 +693,17 @@ contract('LockupVolumeRestrictionTransferManager', accounts => {

// remove all lockups for account 2
let lockUpsLength = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2);
assert.equal(lockUpsLength, 2);
await I_VolumeRestrictionTransferManager.removeLockUp(account_investor2, 0, { from: token_owner });
assert.equal(lockUpsLength, 1);
await I_VolumeRestrictionTransferManager.removeLockUp(account_investor2, 0, { from: token_owner });
lockUpsLength = await I_VolumeRestrictionTransferManager.getLockUpsLength(account_investor2);
assert.equal(lockUpsLength, 0);

let now = latestTime();

// balance here should be 10000000000000000000
// balance here should be 8000000000000000000
let balance = await I_SecurityToken.balanceOf(account_investor2)

await I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 100, 10, now + duration.seconds(4), balance, { from: token_owner });
await I_VolumeRestrictionTransferManager.addLockUp(account_investor2, 80, 10, now + duration.seconds(4), balance, { from: token_owner });

// wait 4 seconds for the lockup to begin
await increaseTime(duration.seconds(4));
Expand All @@ -618,7 +720,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => {
// edit the lockup
let now = latestTime();

// should be 10000000000000000000
// should be 8000000000000000000
let balance = await I_SecurityToken.balanceOf(account_investor2)

// check and get the lockup
Expand All @@ -628,7 +730,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => {
let lockUp = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0);

// elements in lockup array are uint lockUpPeriodSeconds, uint releaseFrequencySeconds, uint startTime, uint totalAmount
assert.equal(lockUp[0].toString(), '100');
assert.equal(lockUp[0].toString(), '80');
assert.equal(lockUp[1].toString(), '10');
assert.isAtMost(lockUp[2].toNumber(), now);
assert.equal(lockUp[3].toString(), balance.toString());
Expand Down Expand Up @@ -665,28 +767,28 @@ contract('LockupVolumeRestrictionTransferManager', accounts => {
await increaseTime(duration.seconds(4));

// try another transfer. it should pass
await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor2 });
await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 });


// try another transfer without waiting for another period to pass. it should fail
await catchRevert(
I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor2 })
I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 })
);

// wait 4 seconds for the lockup's first period to elapse
await increaseTime(duration.seconds(4));

let lockUpBeforeVerify = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0);
// check if transfer will pass in read-only operation
let result = await I_VolumeRestrictionTransferManager.verifyTransfer.call(account_investor2, account_investor1, web3.utils.toWei('5', 'ether'), 0, false)
let result = await I_VolumeRestrictionTransferManager.verifyTransfer.call(account_investor2, account_investor1, web3.utils.toWei('4', 'ether'), 0, false)
// enum Result {INVALID, NA, VALID, FORCE_VALID} and we want VALID so it should be 2
assert.equal(result.toString(), '2')
let lockUpAfterVerify = await I_VolumeRestrictionTransferManager.getLockUp(account_investor2, 0);

assert.equal(lockUpBeforeVerify[4].toString(), lockUpAfterVerify[4].toString())

// try another transfer. it should pass
await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('5', 'ether'), { from: account_investor2 });
await I_SecurityToken.transfer(account_investor1, web3.utils.toWei('4', 'ether'), { from: account_investor2 });

// wait 4 seconds for the lockup's first period to elapse. but, we are all out of periods.
await increaseTime(duration.seconds(4));
Expand All @@ -698,7 +800,7 @@ contract('LockupVolumeRestrictionTransferManager', accounts => {
});

it("Should be possible to stack lockups", async() => {
// should be 17000000000000000000
// should be 18000000000000000000
let balance = await I_SecurityToken.balanceOf(account_investor1)

// check and make sure that acct1 has no lockups so far
Expand All @@ -707,8 +809,9 @@ contract('LockupVolumeRestrictionTransferManager', accounts => {

await I_VolumeRestrictionTransferManager.addLockUp(account_investor1, 12, 4, 0, web3.utils.toWei('6', 'ether'), { from: token_owner });

// try to transfer 11 tokens that aren't locked up yet be locked up. should succeed
await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('11', 'ether'), { from: account_investor1 });

// try to transfer 12 tokens that aren't locked up yet be locked up. should succeed
await I_SecurityToken.transfer(account_investor2, web3.utils.toWei('12', 'ether'), { from: account_investor1 });

// try a transfer. it should fail because it's locked up from the first lockups
await catchRevert(
Expand Down