Skip to content

Commit

Permalink
Merge pull request #657 from PolymathNetwork/port-restricted-partial-…
Browse files Browse the repository at this point in the history
…sale-tm

Port RestrictedPartialSale TM in 3.x
  • Loading branch information
satyamakgec authored Jul 12, 2019
2 parents 0fed502 + d992d41 commit c1e5e50
Show file tree
Hide file tree
Showing 10 changed files with 880 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CLI/commands/helpers/contract_abis.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ let countTransferManagerABI;
let percentageTransferManagerABI;
let lockUpTransferManagerABI;
let volumeRestrictionTMABI;
let restrictedPartialSaleTMABI;
let generalPermissionManagerABI;
let polyTokenABI;
let cappedSTOFactoryABI;
Expand Down Expand Up @@ -46,6 +47,7 @@ try {
percentageTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PercentageTransferManager.json`).toString()).abi;
blacklistTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/BlacklistTransferManager.json`).toString()).abi;
volumeRestrictionTMABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/VolumeRestrictionTM.json`).toString()).abi;
restrictedPartialSaleTMABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/RestrictedPartialSaleTM.json`).toString()).abi;
lockUpTransferManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/LockUpTransferManager.json`).toString()).abi;
generalPermissionManagerABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/GeneralPermissionManager.json`).toString()).abi;
polyTokenABI = JSON.parse(require('fs').readFileSync(`${__dirname}/../../../build/contracts/PolyTokenFaucet.json`).toString()).abi;
Expand Down Expand Up @@ -118,6 +120,9 @@ module.exports = {
volumeRestrictionTM: function () {
return volumeRestrictionTMABI;
},
restrictedPartialSaleTM: function () {
return restrictedPartialSaleTMABI;
},
generalPermissionManager: function () {
return generalPermissionManagerABI;
},
Expand Down
92 changes: 92 additions & 0 deletions CLI/commands/transfer_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const MODIFY_LOCKUP_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/modify_lo
const DELETE_LOCKUP_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/delete_lockup_data.csv`;
const ADD_LOCKUP_INVESTOR_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/add_lockup_investor_data.csv`;
const REMOVE_LOCKUP_INVESTOR_DATA_CSV = `${__dirname}/../data/Transfer/LockupTM/remove_lockup_investor_data.csv`;
const CHANGE_EXEMPT_LIST_DATA_CSV = `${__dirname}/../data/Transfer/RPSTM/change_exempt_list_data.csv`;

const RESTRICTION_TYPES = ['Fixed', 'Percentage'];

Expand Down Expand Up @@ -365,6 +366,11 @@ async function configExistingModules(tmModules) {
currentTransferManager.setProvider(web3.currentProvider);
await volumeRestrictionTM();
break;
case 'RestrictedPartialSaleTM':
currentTransferManager = new web3.eth.Contract(abis.restrictedPartialSaleTM(), tmModules[index].address);
currentTransferManager.setProvider(web3.currentProvider);
await restrictedPartialSaleTM();
break;
}
}

Expand All @@ -385,6 +391,10 @@ async function addTransferManagerModule() {
moduleAbi = abis.percentageTransferManager();
getInitializeData = getPercentageTMInitializeData;
break;
case 'RestrictedPartialSaleTM':
moduleAbi = abis.restrictedPartialSaleTM();
getInitializeData = getRestrictedPartialSaleTM;
break;
}
await common.addModule(securityToken, polyToken, moduleList[index].factoryAddress, moduleAbi, getInitializeData);
}
Expand All @@ -405,6 +415,13 @@ function getCountTMInitializeData(moduleABI) {
return bytes;
}

function getRestrictedPartialSaleTM(moduleABI) {
const treasuryWallet = input.readAddress('Enter the Ethereum address of the treasury wallet to be exempted (or leave empty to use treasury wallet from ST): ', gbl.constants.ADDRESS_ZERO);
const configureRPSTM = moduleABI.find(o => o.name === 'configure' && o.type === 'function');
const bytes = web3.eth.abi.encodeFunctionCall(configureRPSTM, [treasuryWallet]);
return bytes;
}

async function generalTransferManager() {
console.log('\n', chalk.blue(`General Transfer Manager at ${currentTransferManager.options.address}`), '\n');

Expand Down Expand Up @@ -2652,6 +2669,81 @@ async function removeLockupsFromInvestorsInBatch() {
}
}

async function restrictedPartialSaleTM() {
console.log('\n', chalk.blue(`Restriction Partial Sale Transfer Manager at ${currentTransferManager.options.address}`, '\n'));

let exemptedAddresses = await currentTransferManager.methods.getExemptAddresses().call();
console.log(`- Exempted addresses: ${exemptedAddresses.length}`);

let options = [];
if (exemptedAddresses.length > 0) {
options.push('Show exempted addresses');
}
options.push(
'Verify transfer',
'Change exempt wallet',
'Change multiple exemptions',
'Check if account is exempted'
);

let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' });
let optionSelected = index !== -1 ? options[index] : 'RETURN';
console.log('Selected:', optionSelected, '\n');
switch (optionSelected) {
case 'Show exempted addresses':
showExemptedAddresses(exemptedAddresses);
break;
case 'Verify transfer':
await verifyTransfer(true, false);
break;
case 'Change exempt wallet':
await changeExemptWallet();
break;
case 'Change multiple exemptions':
await changeExemptWalletsInBatch();
break;
case 'Check if account is exempted':
await checkIfExempted();
break;
case 'RETURN':
return;
}

await restrictedPartialSaleTM();
}

async function changeExemptWalletsInBatch() {
let csvFilePath = readlineSync.question(`Enter the path for csv data file (${CHANGE_EXEMPT_LIST_DATA_CSV}): `, {
defaultInput: CHANGE_EXEMPT_LIST_DATA_CSV
});
let batchSize = input.readNumberGreaterThan(0, `Enter the max number of records per transaction or batch size (${gbl.constants.DEFAULT_BATCH_SIZE}): `, gbl.constants.DEFAULT_BATCH_SIZE);
let parsedData = csvParse(csvFilePath);
let validData = parsedData.filter(row => web3.utils.isAddress(row[0]) && typeof row[1] === 'boolean');
let invalidRows = parsedData.filter(row => !validData.includes(row));
if (invalidRows.length > 0) {
console.log(chalk.red(`The following lines from csv file are not valid: ${invalidRows.map(r => parsedData.indexOf(r) + 1).join(',')} `));
}
let batches = common.splitIntoBatches(validData, batchSize);
let [holderArray, exemptedArray] = common.transposeBatches(batches);
for (let batch = 0; batch < batches.length; batch++) {
console.log(`Batch ${batch + 1} - Attempting to change the exempted status to the following accounts: \n\n`, holderArray[batch], '\n');
let action = currentTransferManager.methods.changeExemptWalletListMulti(holderArray[batch], exemptedArray[batch]);
let receipt = await common.sendTransaction(action);
console.log(chalk.green('Change exempt list transaction was successful.'));
console.log(`${receipt.gasUsed} gas used.Spent: ${web3.utils.fromWei((new web3.utils.BN(receipt.gasUsed)).mul(new web3.utils.BN(defaultGasPrice)))} ETH`);
}
}

async function checkIfExempted() {
let account = input.readAddress('Enter the account to check: ');
let isExempted = await currentTransferManager.methods.exemptIndex(account).call();
if (isExempted !== '0') {
console.log(chalk.yellow(`${account} is exepmted.`));
} else {
console.log(chalk.green(`${account} is not exepmted.`));
}
}

/*
// Copied from tests
function signData(tmAddress, investorAddress, fromTime, toTime, expiryTime, restricted, validFrom, validTo, pk) {
Expand Down
8 changes: 8 additions & 0 deletions CLI/data/Transfer/RPSTM/change_exempt_list_data.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
0xee7ae74d964f2be7d72c1b187b38e2ed3615d4d1,true
0x2f0fd672bf222413cc69dc1f4f1d7e93ad1763a1,true
0xac297053173b02b02a737d47f7b4a718e5b170ef,true
0x49fc0b78238dab644698a90fa351b4c749e123d2,true
0x10223927009b8add0960359dd90d1449415b7ca9,true
0x3c65cfe3de848cf38e9d76e9c3e57a2f1140b399,true
0xabf60de3265b3017db7a1be66fc8b364ec1dbb98,true
0xb841fe5a89da1bbef2d0805fbd7ffcbbb2fca5e3,true
142 changes: 142 additions & 0 deletions contracts/modules/TransferManager/RPTM/RestrictedPartialSaleTM.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
pragma solidity ^0.5.0;

import "../TransferManager.sol";
import "./RestrictedPartialSaleTMStorage.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";

/**
* @title This TransferManager is used to validate the transaction where partial balance of an
* investor is not allowed to trnasfer beside investor is present in the exemption list
*/
contract RestrictedPartialSaleTM is RestrictedPartialSaleTMStorage, TransferManager {

// Emit when the token holder is added/removed from the exemption list
event ChangedExemptWalletList(address indexed _wallet, bool _exempted);

/**
* @notice Constructor
* @param _securityToken Address of the security token
* @param _polyAddress Address of the polytoken
*/
constructor (address _securityToken, address _polyAddress)
public
Module(_securityToken, _polyAddress)
{
}

/**
* @notice This function returns the signature of configure function
*/
function getInitFunction() public pure returns (bytes4) {
return this.configure.selector;
}

/**
* @notice Used to initialize the variables of the contract
* @param _treasuryWallet Ethereum address of the treasury wallet
*/
function configure(address _treasuryWallet) external onlyFactory {
address treasuryWallet = _treasuryWallet == address(0) ? IDataStore(getDataStore()).getAddress(TREASURY) : _treasuryWallet;
if (treasuryWallet != address(0))
_changeExemptionWalletList(treasuryWallet, true);
}

/**
* @notice Used to verify the transfer transaction and prevent a transfer if it passes the allowed amount of token holders
* @param _from Address of the sender
* @param _amount Amount to send
*/
function executeTransfer(
address _from,
address /* _to */,
uint256 _amount,
bytes calldata /* _data */
)
external
returns (Result)
{
(Result success,)= _verifyTransfer(_from, _amount);
return success;
}

/**
* @notice Used to verify the transfer transaction and prevent a transfer if it passes the allowed amount of token holders
* @param _from Address of the sender
* @param _amount Amount to send
*/
function verifyTransfer(
address _from,
address /* _to */,
uint256 _amount,
bytes calldata /* _data */
)
external
view
returns(Result, bytes32)
{
return _verifyTransfer(_from, _amount);
}

function _verifyTransfer(address _from, uint256 _amount) internal view returns (Result, bytes32) {
if (!paused && _from != address(0) && exemptIndex[_from] == 0) {
if (securityToken.balanceOf(_from) != _amount)
return (Result.INVALID, bytes32(uint256(address(this)) << 96));
}
return (Result.NA, bytes32(0));
}


/**
* @notice Add/Remove wallet address from the exempt list
* @param _wallet Ethereum wallet/contract address that need to be exempted
* @param _exempted Boolean value used to add (i.e true) or remove (i.e false) from the list
*/
function changeExemptWalletList(address _wallet, bool _exempted) external withPerm(OPERATOR) {
_changeExemptionWalletList(_wallet, _exempted);
}

/**
* @notice Add/Remove multiple wallet addresses from the exempt list
* @param _wallet Ethereum wallet/contract addresses that need to be exempted
* @param _exempted Boolean value used to add (i.e true) or remove (i.e false) from the list
*/
function changeExemptWalletListMulti(address[] memory _wallet, bool[] memory _exempted) public withPerm(OPERATOR) {
require(_wallet.length == _exempted.length, "Length mismatch");
for (uint256 i = 0; i < _wallet.length; i++) {
_changeExemptionWalletList(_wallet[i], _exempted[i]);
}
}

function _changeExemptionWalletList(address _wallet, bool _exempted) internal {
require(_wallet != address(0), "Invalid address");
uint256 exemptIndexWallet = exemptIndex[_wallet];
require((exemptIndexWallet == 0) == _exempted, "Exemption state doesn't change");
if (_exempted) {
exemptAddresses.push(_wallet);
exemptIndex[_wallet] = exemptAddresses.length;
} else {
exemptAddresses[exemptIndexWallet - 1] = exemptAddresses[exemptAddresses.length - 1];
exemptIndex[exemptAddresses[exemptIndexWallet - 1]] = exemptIndexWallet;
delete exemptIndex[_wallet];
exemptAddresses.length --;
}
emit ChangedExemptWalletList(_wallet, _exempted);
}

/**
* @notice return the exempted addresses list
*/
function getExemptAddresses() external view returns(address[] memory) {
return exemptAddresses;
}

/**
* @notice Return the permissions flag that are associated with Restricted partial trnasfer manager
* @return bytes32 array
*/
function getPermissions() public view returns(bytes32[] memory) {
bytes32[] memory allPermissions = new bytes32[](1);
allPermissions[0] = OPERATOR;
return allPermissions;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
pragma solidity ^0.5.0;

import "./RestrictedPartialSaleTMProxy.sol";
import "../../UpgradableModuleFactory.sol";

/**
* @title Factory for deploying RestrictedPartialSale module
*/
contract RestrictedPartialSaleTMFactory is UpgradableModuleFactory {

/**
* @notice Constructor
* @param _setupCost Setup cost of the module
* @param _logicContract Contract address that contains the logic related to `description`
* @param _polymathRegistry Address of the Polymath registry
* @param _isCostInPoly true = cost in Poly, false = USD
*/
constructor (
uint256 _setupCost,
address _logicContract,
address _polymathRegistry,
bool _isCostInPoly
)
public
UpgradableModuleFactory("3.1.0", _setupCost, _logicContract, _polymathRegistry, _isCostInPoly)
{
initialVersion = "3.1.0";
name = "RestrictedPartialSaleTM";
title = "Restricted Partial Sale Transfer Manager";
description = "TM will not allow investors to transact partial balance of the investors";
typesData.push(2);
tagsData.push("PartialSale");
tagsData.push("Transfer Restriction");
tagsData.push("Restricted transfer");
compatibleSTVersionRange["lowerBound"] = VersionUtils.pack(uint8(3), uint8(0), uint8(0));
compatibleSTVersionRange["upperBound"] = VersionUtils.pack(uint8(3), uint8(1), uint8(0));
}

/**
* @notice Used to launch the Module with the help of factory
* @return address Contract address of the Module
*/
function deploy(
bytes calldata _data
)
external
returns(address)
{
address restrictedPartialSaleTM = address(
new RestrictedPartialSaleTMProxy(
logicContracts[latestUpgrade].version,
msg.sender,
polymathRegistry.getAddress("PolyToken"),
logicContracts[latestUpgrade].logicContract
)
);
_initializeModule(restrictedPartialSaleTM, _data);
return restrictedPartialSaleTM;
}


}
Loading

0 comments on commit c1e5e50

Please sign in to comment.