Skip to content
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
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ All notable changes to this project will be documented in this file.
* Removed individual mappings for tier data removed in UDSTSTO.
* Removed the old Proxy deployment method of USDTieredSTO and adopt the new inherited proxy deployment approach.
* Bump the version to `2.1.0`
* Added `getAccreditedData` to return accredited & non-accredited investor data

## GeneralTransferManager
* `getInvestors`, `getAllInvestorsData`, `getInvestorsData` added to GTM to allow easy data queries.
Expand All @@ -31,7 +32,7 @@ All notable changes to this project will be documented in this file.
* Changed the version of `GeneralTransferManagerFactory` from `1.0.0` to `2.1.0`.

## Manual Approval TransferManager
* Removed `0x0` check for the `_from` address to `ManualApprovalTransferManager`. This allows for the Issuer/Transfer Agent to approve a one-off mint of tokens that otherwise would not be possible.
* Removed `0x0` check for the `_from` address to `ManualApprovalTransferManager`. This allows for the Issuer/Transfer Agent to approve a one-off mint of tokens that otherwise would not be possible.
* Changed the version of `ManualApprovalTransferManagerFactory` from `1.0.0` to `2.1.0`.
* Deployed 2.0.1 `ManualApprovalTransferManagerFactory` to address 0x6af2afad53cb334e62b90ddbdcf3a086f654c298
* Add `getActiveApprovalsToUser()` function to access all the active approvals for a user whether user is in the `from` or in `to`.
Expand Down Expand Up @@ -97,7 +98,7 @@ volume traded in a given rolling period.
* Removed investors list pruning
* Remove `swarmHash` from the `registerTicker(), addCustomTicker(), generateSecurityToken(), addCustomSecurityToken()` functions of TickerRegistry.sol and SecurityTokenRegistry.sol. #230
* Remove `Log` prefix from all the event present in the ecosystem.
* Removed `addTagByModuleType` & `removeTagsByModuleType` from MR.
* Removed `addTagByModuleType` & `removeTagsByModuleType` from MR.

======

Expand Down
18 changes: 9 additions & 9 deletions CLI/commands/investor_portal.js
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ async function processAddressWithBalance(array) {
for (const address of array) {
let symbol = await checkSymbol(address);
let balance = await checkBalance(address);
list.push({'address': address, 'symbol': symbol, 'balance': balance})
list.push({ 'address': address, 'symbol': symbol, 'balance': balance })
}
return list
}
Expand All @@ -254,7 +254,7 @@ async function processAddress(array) {
let list = [];
for (const address of array) {
let symbol = await checkSymbol(address);
list.push({"symbol": symbol, "address": address})
list.push({ "symbol": symbol, "address": address })
}
return list
}
Expand All @@ -277,8 +277,7 @@ async function checkBalance(address) {
}
}

async function showUserInfoForUSDTieredSTO()
{
async function showUserInfoForUSDTieredSTO() {
let stableSymbols = [];
let listOfStableCoins = await currentSTO.methods.getUsdTokens().call();

Expand Down Expand Up @@ -306,11 +305,12 @@ async function showUserInfoForUSDTieredSTO()
console.log(` - Whitelisted: ${(displayCanBuy) ? 'YES' : 'NO'}`);
console.log(` - Valid KYC: ${(displayValidKYC) ? 'YES' : 'NO'}`);

let displayIsUserAccredited = await currentSTO.methods.accredited(User.address).call();
let investorData = await currentSTO.methods.investors(User.address).call();
let displayIsUserAccredited = investorData.accredited == 1;
console.log(` - Accredited: ${(displayIsUserAccredited) ? "YES" : "NO"}`)

if (!await currentSTO.methods.accredited(User.address).call()) {
let displayOverrideNonAccreditedLimitUSD = web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSDOverride(User.address).call())
if (!displayIsUserAccredited) {
let displayOverrideNonAccreditedLimitUSD = web3.utils.fromWei(investorData.nonAccreditedLimitUSDOverride);
let displayNonAccreditedLimitUSD = displayOverrideNonAccreditedLimitUSD != 0 ? displayOverrideNonAccreditedLimitUSD : web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSD().call());
let displayTokensRemainingAllocation = displayNonAccreditedLimitUSD - displayInvestorInvestedUSD;
console.log(` - Remaining allocation: ${(displayTokensRemainingAllocation > 0 ? displayTokensRemainingAllocation : 0)} USD`);
Expand Down Expand Up @@ -402,7 +402,7 @@ async function showUSDTieredSTOInfo() {
})
} else {
displayFundsRaisedPerType += `
${type}:\t\t\t ${fundsRaised} ${type}`;
${type}:\t\t\t ${fundsRaised} ${type}`;
}
//Only show sold per raise type is more than one are allowed
if (raiseTypes.length > 1) {
Expand All @@ -420,7 +420,7 @@ async function showUSDTieredSTOInfo() {
let displayRaiseType = raiseTypes.join(' - ');
//If STO has stable coins, we list them one by one
if (stableSymbols.length) {
displayRaiseType = displayRaiseType.replace(STABLE, "") + `${stableSymbols.map((obj) => {return obj.symbol}).toString().replace(`,`,` - `)}`
displayRaiseType = displayRaiseType.replace(STABLE, "") + `${stableSymbols.map((obj) => { return obj.symbol }).toString().replace(`,`, ` - `)}`
}

let now = Math.floor(Date.now() / 1000);
Expand Down
74 changes: 51 additions & 23 deletions CLI/commands/sto_manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const abis = require('./helpers/contract_abis');
const common = require('./common/common_functions');
const gbl = require('./common/global');
const csvParse = require('./helpers/csv');
const { table } = require('table');
const STABLE = 'STABLE';

///////////////////
Expand Down Expand Up @@ -45,8 +46,8 @@ async function executeApp() {
}
options.push('Add new STO module');

let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'Exit' });
let optionSelected = index != -1 ? options[index] : 'Exit';
let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'EXIT' });
let optionSelected = index != -1 ? options[index] : 'EXIT';
console.log('Selected:', optionSelected, '\n');
switch (optionSelected) {
case 'Show existing STO information':
Expand All @@ -60,7 +61,7 @@ async function executeApp() {
case 'Add new STO module':
await addSTOModule();
break;
case 'Exit':
case 'EXIT':
exit = true;
break;
}
Expand Down Expand Up @@ -97,6 +98,7 @@ async function showSTO(selectedSTO, currentSTO) {
break;
case 'USDTieredSTO':
await usdTieredSTO_status(currentSTO);
await showAccreditedData(currentSTO);
break;
}
}
Expand Down Expand Up @@ -126,8 +128,8 @@ async function addSTOModule(stoConfig) {
let moduleFactory = new web3.eth.Contract(moduleFactoryABI, m);
return web3.utils.hexToUtf8(await moduleFactory.methods.name().call());
}));
let index = readlineSync.keyInSelect(options, 'What type of STO do you want?', { cancel: 'Return' });
optionSelected = index != -1 ? options[index] : 'Return';
let index = readlineSync.keyInSelect(options, 'What type of STO do you want?', { cancel: 'RETURN' });
optionSelected = index != -1 ? options[index] : 'RETURN';
} else {
optionSelected = stoConfig.type;
}
Expand Down Expand Up @@ -271,8 +273,6 @@ async function cappedSTO_status(currentSTO) {
- Tokens remaining: ${web3.utils.fromWei(displayCap.sub(displayTokensSold))} ${displayTokenSymbol.toUpperCase()}
- Investor count: ${displayInvestorCount}
`);

console.log(chalk.green(`\n${(web3.utils.fromWei(await getBalance(Issuer.address, gbl.constants.FUND_RAISE_TYPES.POLY)))} POLY balance remaining at issuer address ${Issuer.address}`));
}

////////////////////
Expand Down Expand Up @@ -579,6 +579,7 @@ async function usdTieredSTO_status(currentSTO) {
let displayStartTime = await currentSTO.methods.startTime().call();
let displayEndTime = await currentSTO.methods.endTime().call();
let displayCurrentTier = parseInt(await currentSTO.methods.currentTier().call()) + 1;
let test = await currentSTO.methods.nonAccreditedLimitUSD().call();
let displayNonAccreditedLimitUSD = web3.utils.fromWei(await currentSTO.methods.nonAccreditedLimitUSD().call());
let displayMinimumInvestmentUSD = web3.utils.fromWei(await currentSTO.methods.minimumInvestmentUSD().call());
let displayWallet = await currentSTO.methods.wallet().call();
Expand Down Expand Up @@ -752,8 +753,6 @@ async function usdTieredSTO_status(currentSTO) {
+ displayFundsRaisedPerType + `
Total USD: ${displayFundsRaisedUSD} USD
`);

console.log(chalk.green(`\n${(web3.utils.fromWei(await getBalance(Issuer.address, gbl.constants.FUND_RAISE_TYPES.POLY)))} POLY balance remaining at issuer address ${Issuer.address}`));
}

async function checkStableBalance(walletAddress, stableAddress) {
Expand Down Expand Up @@ -789,9 +788,10 @@ async function usdTieredSTO_configure(currentSTO) {
'Modify limits configuration', 'Modify funding configuration');
}

let index = readlineSync.keyInSelect(options, 'What do you want to do?');
switch (index) {
case 0:
let index = readlineSync.keyInSelect(options, 'What do you want to do?', { cancel: 'RETURN' });
let selected = index != -1 ? options[index] : 'Exit';
switch (selected) {
case 'Finalize STO':
let reserveWallet = await currentSTO.methods.reserveWallet().call();
let isVerified = await securityToken.methods.verifyTransfer('0x0000000000000000000000000000000000000000', reserveWallet, 0, web3.utils.fromAscii("")).call();
if (isVerified) {
Expand All @@ -803,7 +803,7 @@ async function usdTieredSTO_configure(currentSTO) {
console.log(chalk.red(`Reserve wallet (${reserveWallet}) is not able to receive remaining tokens. Check if this address is whitelisted.`));
}
break;
case 1:
case 'Change accredited account':
let investor = readlineSync.question('Enter the address to change accreditation: ');
let isAccredited = readlineSync.keyInYNStrict(`Is ${investor} accredited?`);
let investors = [investor];
Expand All @@ -812,44 +812,71 @@ async function usdTieredSTO_configure(currentSTO) {
// 2 GAS?
await common.sendTransaction(changeAccreditedAction);
break;
case 2:
case 'Change accredited in batch':
await changeAccreditedInBatch(currentSTO);
break;
case 3:
case 'Change non accredited limit for an account':
let account = readlineSync.question('Enter the address to change non accredited limit: ');
let limit = readlineSync.question(`Enter the limit in USD: `);
let accounts = [account];
let limits = [web3.utils.toWei(limit)];
let changeNonAccreditedLimitAction = currentSTO.methods.changeNonAccreditedLimit(accounts, limits);
await common.sendTransaction(changeNonAccreditedLimitAction);
break;
case 4:
case 'Change non accredited limits in batch':
await changeNonAccreditedLimitsInBatch(currentSTO);
break;
case 5:
case 'Modify times configuration':
await modfifyTimes(currentSTO);
await usdTieredSTO_status(currentSTO);
break;
case 6:
case 'Modify tiers configuration':
await modfifyTiers(currentSTO);
await usdTieredSTO_status(currentSTO);
break;
case 7:
case 'Modify addresses configuration':
await modfifyAddresses(currentSTO);
await usdTieredSTO_status(currentSTO);
break;
case 8:
case 'Modify limits configuration':
await modfifyLimits(currentSTO);
await usdTieredSTO_status(currentSTO);
break;
case 9:
case 'Modify funding configuration':
await modfifyFunding(currentSTO);
await usdTieredSTO_status(currentSTO);
break;
}
}
}

async function showAccreditedData(currentSTO) {
let accreditedData = await currentSTO.methods.getAccreditedData().call();
let investorArray = accreditedData[0];
let accreditedArray = accreditedData[1];
let nonAccreditedLimitArray = accreditedData[2];

if (investorArray.length > 0) {
let dataTable = [['Investor', 'Is accredited', 'Non-accredited limit (USD)']];
for (let i = 0; i < investorArray.length; i++) {
dataTable.push([
investorArray[i],
accreditedArray[i] ? 'YES' : 'NO',
accreditedArray[i] ? 'N/A' : (nonAccreditedLimitArray[i] !== '0' ? web3.utils.fromWei(nonAccreditedLimitArray[i]) : 'default')
]);
}
console.log();
console.log(`************************************ ACCREDITED DATA *************************************`);
console.log();
console.log(table(dataTable));
} else {
console.log();
console.log(chalk.yellow(`There is no accredited data to show`));
console.log();
}

}

async function changeAccreditedInBatch(currentSTO) {
let csvFilePath = readlineSync.question(`Enter the path for csv data file (${ACCREDIT_DATA_CSV}): `, {
defaultInput: ACCREDIT_DATA_CSV
Expand Down Expand Up @@ -898,6 +925,7 @@ async function changeNonAccreditedLimitsInBatch(currentSTO) {
let batches = common.splitIntoBatches(validData, batchSize);
let [investorArray, limitArray] = common.transposeBatches(batches);
for (let batch = 0; batch < batches.length; batch++) {
limitArray[batch] = limitArray[batch].map(a => web3.utils.toWei(new web3.utils.BN(a)));
console.log(`Batch ${batch + 1} - Attempting to change non accredited limit to accounts:\n\n`, investorArray[batch], '\n');
let action = currentSTO.methods.changeNonAccreditedLimit(investorArray[batch], limitArray[batch]);
let receipt = await common.sendTransaction(action);
Expand Down Expand Up @@ -1052,8 +1080,8 @@ async function selectToken() {
});
options.push('Enter token symbol manually');

let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'Exit' });
let selected = index != -1 ? options[index] : 'Exit';
let index = readlineSync.keyInSelect(options, 'Select a token:', { cancel: 'EXIT' });
let selected = index != -1 ? options[index] : 'EXIT';
switch (selected) {
case 'Enter token symbol manually':
result = readlineSync.question('Enter the token symbol: ');
Expand Down
39 changes: 34 additions & 5 deletions contracts/modules/STO/USDTieredSTO.sol
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,12 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard {
function changeAccredited(address[] _investors, bool[] _accredited) public onlyOwner {
require(_investors.length == _accredited.length, "Array length mismatch");
for (uint256 i = 0; i < _investors.length; i++) {
accredited[_investors[i]] = _accredited[i];
if (_accredited[i]) {
investors[_investors[i]].accredited = uint8(1);
} else {
investors[_investors[i]].accredited = uint8(0);
}
_addToInvestorsList(_investors[i]);
emit SetAccredited(_investors[i], _accredited[i]);
}
}
Expand All @@ -320,12 +325,36 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard {
//nonAccreditedLimitUSDOverride
require(_investors.length == _nonAccreditedLimit.length, "Array length mismatch");
for (uint256 i = 0; i < _investors.length; i++) {
require(_nonAccreditedLimit[i] > 0, "Limit = 0");
nonAccreditedLimitUSDOverride[_investors[i]] = _nonAccreditedLimit[i];
investors[_investors[i]].nonAccreditedLimitUSDOverride = _nonAccreditedLimit[i];
_addToInvestorsList(_investors[i]);
emit SetNonAccreditedLimit(_investors[i], _nonAccreditedLimit[i]);
}
}

function _addToInvestorsList(address _investor) internal {
if (investors[_investor].seen == uint8(0)) {
investors[_investor].seen = uint8(1);
investorsList.push(_investor);
}
}

/**
* @notice Returns investor accredited & non-accredited override informatiomn
* @return address[] list of all configured investors
* @return bool[] whether investor is accredited
* @return uint256[] any USD overrides for non-accredited limits for the investor
*/
function getAccreditedData() external view returns (address[], bool[], uint256[]) {
bool[] memory accrediteds = new bool[](investorsList.length);
uint256[] memory nonAccreditedLimitUSDOverrides = new uint256[](investorsList.length);
uint256 i;
for (i = 0; i < investorsList.length; i++) {
accrediteds[i] = (investors[investorsList[i]].accredited == uint8(0)? false: true);
nonAccreditedLimitUSDOverrides[i] = investors[investorsList[i]].nonAccreditedLimitUSDOverride;
}
return (investorsList, accrediteds, nonAccreditedLimitUSDOverrides);
}

/**
* @notice Function to set allowBeneficialInvestments (allow beneficiary to be different to funder)
* @param _allowBeneficialInvestments Boolean to allow or disallow beneficial investments
Expand Down Expand Up @@ -525,8 +554,8 @@ contract USDTieredSTO is USDTieredSTOStorage, ISTO, ReentrancyGuard {
require(investedUSD.add(investorInvestedUSD[_beneficiary]) >= minimumInvestmentUSD, "Total investment < minimumInvestmentUSD");
netInvestedUSD = investedUSD;
// Check for non-accredited cap
if (!accredited[_beneficiary]) {
uint256 investorLimitUSD = (nonAccreditedLimitUSDOverride[_beneficiary] == 0) ? nonAccreditedLimitUSD : nonAccreditedLimitUSDOverride[_beneficiary];
if (investors[_beneficiary].accredited == uint8(0)) {
uint256 investorLimitUSD = (investors[_beneficiary].nonAccreditedLimitUSDOverride == 0) ? nonAccreditedLimitUSD : investors[_beneficiary].nonAccreditedLimitUSDOverride;
require(investorInvestedUSD[_beneficiary] < investorLimitUSD, "Over Non-accredited investor limit");
if (investedUSD.add(investorInvestedUSD[_beneficiary]) > investorLimitUSD)
netInvestedUSD = investorLimitUSD.sub(investorInvestedUSD[_beneficiary]);
Expand Down
22 changes: 16 additions & 6 deletions contracts/modules/STO/USDTieredSTOStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@ contract USDTieredSTOStorage {
uint256 mintedDiscountPoly;
}

struct Investor {
// Whether investor is accredited (0 = non-accredited, 1 = accredited)
uint8 accredited;
// Whether we have seen the investor before (already added to investors list)
uint8 seen;
// Overrides for default limit in USD for non-accredited investors multiplied by 10**18 (0 = no override)
uint256 nonAccreditedLimitUSDOverride;
}

mapping (bytes32 => mapping (bytes32 => string)) oracleKeys;

// Determine whether users can invest on behalf of a beneficiary
Expand Down Expand Up @@ -66,18 +75,19 @@ contract USDTieredSTOStorage {
// Amount in fund raise type invested by each investor
mapping (address => mapping (uint8 => uint256)) public investorInvested;

// List of accredited investors
mapping (address => bool) public accredited;
// Accredited & non-accredited investor data
mapping (address => Investor) public investors;

// List of active stable coin addresses
mapping (address => bool) public usdTokenEnabled;

// List of all addresses that have been added as accredited or non-accredited without
// the default limit
address[] public investorsList;

// Default limit in USD for non-accredited investors multiplied by 10**18
uint256 public nonAccreditedLimitUSD;

// Overrides for default limit in USD for non-accredited investors multiplied by 10**18
mapping (address => uint256) public nonAccreditedLimitUSDOverride;

// Minimum investable amount in USD
uint256 public minimumInvestmentUSD;

Expand All @@ -87,4 +97,4 @@ contract USDTieredSTOStorage {
// Array of Tiers
Tier[] public tiers;

}
}
Loading