Skip to content

Commit

Permalink
feat: [W12-208] individual purchase fee for payment methods (#78)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hokid authored Dec 12, 2018
1 parent 97e00fb commit cbb71aa
Show file tree
Hide file tree
Showing 10 changed files with 487 additions and 104 deletions.
68 changes: 47 additions & 21 deletions contracts/W12Lister.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract W12Lister is IAdminRole, AdminRole, Versionable, Secondary, ReentrancyG
TokenListing.Whitelist whitelist;

event OwnerWhitelisted(address indexed tokenAddress, address indexed tokenOwner);
event TokenWhitelisted(address indexed token, address indexed sender, address[] owners, string name, string symbol);
event TokenWhitelisted(address indexed token, address indexed sender, address[] owners);
event TokenPlaced(address indexed originalToken, address indexed sender, address crowdsale, uint tokenAmount, address placedToken);
event CrowdsaleInitialized(address indexed token, address indexed sender, address crowdsale, uint amountForSale);
event CrowdsaleTokenMinted(address indexed token, address indexed sender, address crowdsale, uint amount);
Expand Down Expand Up @@ -65,10 +65,9 @@ contract W12Lister is IAdminRole, AdminRole, Versionable, Secondary, ReentrancyG
string symbol,
uint8 decimals,
address[] owners,
uint feePercent,
uint ethFeePercent,
uint WTokenSaleFeePercent,
uint trancheFeePercent
uint[4] commissions, // [feePercent, ethFeePercent, WTokenSaleFeePercent, trancheFeePercent]
bytes32[] paymentMethods,
uint[] paymentMethodsPurchaseFee
)
external onlyAdmin
{
Expand All @@ -78,13 +77,12 @@ contract W12Lister is IAdminRole, AdminRole, Versionable, Secondary, ReentrancyG
symbol,
decimals,
owners,
feePercent,
ethFeePercent,
WTokenSaleFeePercent,
trancheFeePercent
commissions,
paymentMethods,
paymentMethodsPurchaseFee
);

emit TokenWhitelisted(token, msg.sender, owners, name, symbol);
emit TokenWhitelisted(token, msg.sender, owners);
}

/**
Expand Down Expand Up @@ -171,21 +169,31 @@ contract W12Lister is IAdminRole, AdminRole, Versionable, Secondary, ReentrancyG
wtoken.addAdmin(address(crowdsale));

whitelist.initializeCrowdsale(token, address(crowdsale));

if (listedToken.WTokenSaleFeePercent > 0) {
exchanger.approve(
IERC20(listedToken.token),
address(crowdsale),
whitelist.getCrowdsaleByAddress(token, address(crowdsale)).tokensForSaleAmount
.percent(listedToken.WTokenSaleFeePercent)
);
}
_setPaymentMethodPurchaseFeeForCrowdsale(crowdsale, listedToken);

// give approve to spend entire tokens sale amount because there may be
// individual purchase fee value for a payment method and it may be changed in the future.
exchanger.approve(
IERC20(listedToken.token),
address(crowdsale),
whitelist.getCrowdsaleByAddress(token, address(crowdsale)).tokensForSaleAmount
);

addTokensToCrowdsale(token, address(crowdsale), amountForSale);

emit CrowdsaleInitialized(listedToken.token, msg.sender, address(crowdsale), amountForSale);
}

function _setPaymentMethodPurchaseFeeForCrowdsale(IW12Crowdsale crowdsale, TokenListing.WhitelistedToken storage listedToken) private {
for(uint i = 0; i < listedToken.paymentMethods.length; i++) {
crowdsale.updatePurchaseFeeParameterForPaymentMethod(
listedToken.paymentMethods[i],
true,
listedToken.paymentMethodsPurchaseFee[i]
);
}
}

function addTokensToCrowdsale(address token, address crowdsale, uint amountForSale) public {
require(amountForSale > 0);
require(whitelist.isTokenWhitelisted(token));
Expand Down Expand Up @@ -221,7 +229,15 @@ contract W12Lister is IAdminRole, AdminRole, Versionable, Secondary, ReentrancyG
}

function getToken(address token)
external view returns (string name, string symbol, uint8 decimals, address[] owners, uint[4] commissions)
external view returns (
string name,
string symbol,
uint8 decimals,
address[] owners,
uint[4] commissions,
bytes32[] paymentMethods,
uint[] paymentMethodsPurchaseFee
)
{
require(whitelist.isTokenWhitelisted(token));

Expand All @@ -233,6 +249,8 @@ contract W12Lister is IAdminRole, AdminRole, Versionable, Secondary, ReentrancyG
commissions[1] = whitelist.getToken(token).ethFeePercent;
commissions[2] = whitelist.getToken(token).WTokenSaleFeePercent;
commissions[3] = whitelist.getToken(token).trancheFeePercent;
paymentMethods = whitelist.getToken(token).paymentMethods;
paymentMethodsPurchaseFee = whitelist.getToken(token).paymentMethodsPurchaseFee;
}

function getTokens() external view returns (address[]) {
Expand Down Expand Up @@ -272,7 +290,13 @@ contract W12Lister is IAdminRole, AdminRole, Versionable, Secondary, ReentrancyG
}

function getCrowdsale(address token, address crowdsale)
external view returns (uint[4] commissions, uint[2] amounts, address[] owners)
external view returns (
uint[4] commissions,
uint[2] amounts,
address[] owners,
bytes32[] paymentMethods,
uint[] paymentMethodsPurchaseFee
)
{
require(whitelist.hasCrowdsaleWithAddress(token, crowdsale));

Expand All @@ -283,6 +307,8 @@ contract W12Lister is IAdminRole, AdminRole, Versionable, Secondary, ReentrancyG
amounts[0] = whitelist.getCrowdsaleByAddress(token, crowdsale).tokensForSaleAmount;
amounts[1] = whitelist.getCrowdsaleByAddress(token, crowdsale).wTokensIssuedAmount;
owners = whitelist.getCrowdsaleByAddress(token, crowdsale).owners;
paymentMethods = whitelist.getCrowdsaleByAddress(token, crowdsale).paymentMethods;
paymentMethodsPurchaseFee = whitelist.getCrowdsaleByAddress(token, crowdsale).paymentMethodsPurchaseFee;
}

function serviceWallet() public view returns(address) {
Expand Down
6 changes: 6 additions & 0 deletions contracts/crowdsale/IW12Crowdsale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@ contract IW12Crowdsale is IAdminRole, IProjectOwnerRole {

function isSaleActive() public view returns (bool);

function updatePurchaseFeeParameterForPaymentMethod(bytes32 method, bool has, uint value) public;

function getPurchaseFeeParameterForPaymentMethod(bytes32 method) public view returns (bool, uint);

function getPurchaseFeeForPaymentMethod(bytes32 method) public view returns (uint);

function buyTokens(bytes32 method, uint amount) payable public;

function transferPrimary(address _address) public;
Expand Down
36 changes: 33 additions & 3 deletions contracts/crowdsale/W12Crowdsale.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ contract W12Crowdsale is IW12Crowdsale, AdminRole, ProjectOwnerRole, Versionable
using Percent for uint;
using PaymentMethods for PaymentMethods.Methods;

struct PaymentMethodPurchaseFee {
bool has;
uint value;
}

IWToken public token;
IERC20 public originToken;
IW12Fund public fund;
Expand All @@ -35,6 +40,7 @@ contract W12Crowdsale is IW12Crowdsale, AdminRole, ProjectOwnerRole, Versionable

// list of payment methods
PaymentMethods.Methods paymentMethods;
mapping(bytes32 => PaymentMethodPurchaseFee) private paymentMethodsPurchaseFee;

Crowdsale.Stage[] public stages;
Crowdsale.Milestone[] public milestones;
Expand Down Expand Up @@ -164,6 +170,23 @@ contract W12Crowdsale is IW12Crowdsale, AdminRole, ProjectOwnerRole, Versionable
__setParameters(_price, serviceWallet);
}

function updatePurchaseFeeParameterForPaymentMethod(bytes32 method, bool has, uint value) public onlyAdmin {
require(value.isPercent() && value < Percent.MAX());
paymentMethodsPurchaseFee[method].has = has;
paymentMethodsPurchaseFee[method].value = value;
}

function getPurchaseFeeParameterForPaymentMethod(bytes32 method) public view returns(bool, uint) {
return (paymentMethodsPurchaseFee[method].has, paymentMethodsPurchaseFee[method].value);
}

function getPurchaseFeeForPaymentMethod(bytes32 method) public view returns(uint) {
if (paymentMethodsPurchaseFee[method].has) {
return paymentMethodsPurchaseFee[method].value;
}
return serviceFee;
}

/**
* @dev Setup all crowdsale parameters at a time
*/
Expand Down Expand Up @@ -255,14 +278,16 @@ contract W12Crowdsale is IW12Crowdsale, AdminRole, ProjectOwnerRole, Versionable
}

function buyTokens(bytes32 method, uint amount) payable public nonReentrant onlyWhenSaleActive {
require(paymentMethods.isAllowed(method));

if (PurchaseProcessing.METHOD_ETH() != method) {
require(rates.getTokenAddress(method) != address(0));
}

(uint index, /*bool found*/) = getCurrentStageIndex();

uint[5] memory invoice = getInvoice(method, amount);
uint[2] memory fee = getFee(invoice[0], invoice[1]);
uint[2] memory fee = getFee(invoice[0], invoice[1], method);

_transferFee(fee, method);
_transferPurchase(invoice, fee, stages[index].vesting, method);
Expand Down Expand Up @@ -320,6 +345,8 @@ contract W12Crowdsale is IW12Crowdsale, AdminRole, ProjectOwnerRole, Versionable
}

function getInvoice(bytes32 method, uint amount) public view returns (uint[5]) {
require(paymentMethods.isAllowed(method));

(uint index, bool found) = getCurrentStageIndex();

if (!found) return;
Expand Down Expand Up @@ -357,6 +384,8 @@ contract W12Crowdsale is IW12Crowdsale, AdminRole, ProjectOwnerRole, Versionable
}

function getInvoiceByTokenAmount(bytes32 method, uint tokenAmount) public view returns (uint[4]) {
require(paymentMethods.isAllowed(method));

(uint index, bool found) = getCurrentStageIndex();

if (!found) return;
Expand Down Expand Up @@ -393,8 +422,9 @@ contract W12Crowdsale is IW12Crowdsale, AdminRole, ProjectOwnerRole, Versionable
result[4] = token.balanceOf(address(this));
}

function getFee(uint tokenAmount, uint cost) public view returns(uint[2]) {
return PurchaseProcessing.fee(tokenAmount, cost, serviceFee, WTokenSaleFeePercent);
function getFee(uint tokenAmount, uint cost, bytes32 method) public view returns(uint[2]) {
require(paymentMethods.isAllowed(method));
return PurchaseProcessing.fee(tokenAmount, cost, WTokenSaleFeePercent, getPurchaseFeeForPaymentMethod(method));
}

function getSaleVolumeBonus(uint value) public view returns(uint bonus) {
Expand Down
71 changes: 53 additions & 18 deletions contracts/libs/TokenListing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ library TokenListing {
uint WTokenSaleFeePercent;
uint trancheFeePercent;
address token;
bytes32[] paymentMethods;
uint[] paymentMethodsPurchaseFee;
mapping(address => bool) _hasOwner;
}

Expand All @@ -33,6 +35,8 @@ library TokenListing {
uint tokensForSaleAmount;
uint wTokensIssuedAmount;
address[] owners;
bytes32[] paymentMethods;
uint[] paymentMethodsPurchaseFee;
}

struct Whitelist {
Expand Down Expand Up @@ -144,7 +148,9 @@ library TokenListing {
trancheFeePercent : 0,
tokensForSaleAmount : 0,
wTokensIssuedAmount : 0,
owners : new address[](0)
owners : new address[](0),
paymentMethods: new bytes32[](0),
paymentMethodsPurchaseFee: new uint[](0)
})
);
}
Expand All @@ -160,26 +166,49 @@ library TokenListing {
emit OwnerWhitelisted(token, owner);
}

function _validatePaymentMethodsParameters(
bytes32[] paymentMethods,
uint[] paymentMethodsPurchaseFee
)
private pure returns(bool)
{
if(paymentMethods.length == paymentMethodsPurchaseFee.length) {
for(uint i = 0; i < paymentMethods.length; i++) {
if (!paymentMethodsPurchaseFee[i].isPercent() || paymentMethodsPurchaseFee[i] > Percent.MAX()) {
return false;
}
// this better way then having mapping in some case
for(uint ii = i + 1; ii < paymentMethods.length; ii++) {
if (paymentMethods[i] == paymentMethods[ii]) {
return false;
}
}
}
return true;
}
return false;
}

function addOrUpdate(
Whitelist storage whitelist,
address token,
string name,
string symbol,
uint8 decimals,
address[] tokenOwners,
uint feePercent,
uint ethFeePercent,
uint WTokenSaleFeePercent,
uint trancheFeePercent
uint[4] commissions, // [feePercent, ethFeePercent, WTokenSaleFeePercent, trancheFeePercent]
bytes32[] paymentMethods,
uint[] paymentMethodsPurchaseFee
)
internal returns(uint)
{
require(token != address(0));
require(tokenOwners.length > 0);
require(feePercent.isPercent() && feePercent.fromPercent() < 100);
require(ethFeePercent.isPercent() && ethFeePercent.fromPercent() < 100);
require(WTokenSaleFeePercent.isPercent() && WTokenSaleFeePercent.fromPercent() < 100);
require(trancheFeePercent.isPercent() && trancheFeePercent.fromPercent() < 100);
require(commissions[0].isPercent() && commissions[0].fromPercent() < 100);
require(commissions[1].isPercent() && commissions[1].fromPercent() < 100);
require(commissions[2].isPercent() && commissions[2].fromPercent() < 100);
require(commissions[3].isPercent() && commissions[3].fromPercent() < 100);
require(_validatePaymentMethodsParameters(paymentMethods, paymentMethodsPurchaseFee));

if (!isTokenWhitelisted(whitelist, token)) {
whitelist._index[token] = whitelist._list.length;
Expand All @@ -190,22 +219,26 @@ library TokenListing {
symbol: symbol,
decimals: decimals,
owners: new address[](0),
feePercent: feePercent,
ethFeePercent: ethFeePercent,
WTokenSaleFeePercent: WTokenSaleFeePercent,
trancheFeePercent: trancheFeePercent,
token: token
feePercent: commissions[0],
ethFeePercent: commissions[1],
WTokenSaleFeePercent: commissions[2],
trancheFeePercent: commissions[3],
token: token,
paymentMethods: paymentMethods,
paymentMethodsPurchaseFee: paymentMethodsPurchaseFee
})
);
} else {
_removeOwners(getToken(whitelist, token));
getToken(whitelist, token).name = name;
getToken(whitelist, token).symbol = symbol;
getToken(whitelist, token).decimals = decimals;
getToken(whitelist, token).feePercent = feePercent;
getToken(whitelist, token).ethFeePercent = ethFeePercent;
getToken(whitelist, token).WTokenSaleFeePercent = WTokenSaleFeePercent;
getToken(whitelist, token).trancheFeePercent = trancheFeePercent;
getToken(whitelist, token).feePercent = commissions[0];
getToken(whitelist, token).ethFeePercent = commissions[1];
getToken(whitelist, token).WTokenSaleFeePercent = commissions[2];
getToken(whitelist, token).trancheFeePercent = commissions[3];
getToken(whitelist, token).paymentMethods = paymentMethods;
getToken(whitelist, token).paymentMethodsPurchaseFee = paymentMethodsPurchaseFee;
}

for (uint i = 0; i < tokenOwners.length; i++) {
Expand All @@ -231,6 +264,8 @@ library TokenListing {
getNotInitialisedCrowdsale(whitelist, token).WTokenSaleFeePercent = getToken(whitelist, token).WTokenSaleFeePercent;
getNotInitialisedCrowdsale(whitelist, token).trancheFeePercent = getToken(whitelist, token).trancheFeePercent;
getNotInitialisedCrowdsale(whitelist, token).owners = getToken(whitelist, token).owners;
getNotInitialisedCrowdsale(whitelist, token).paymentMethods = getToken(whitelist, token).paymentMethods;
getNotInitialisedCrowdsale(whitelist, token).paymentMethodsPurchaseFee = getToken(whitelist, token).paymentMethodsPurchaseFee;

whitelist._hasCrowdsaleIndex[token][crowdsale] = true;
whitelist._crowdsaleIndex[token][crowdsale] = getCrowdsaleIndexByAddress(whitelist, token, address(0));
Expand Down
6 changes: 6 additions & 0 deletions contracts/mocks/crowdsale/W12FundCrowdsaleStub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ contract W12FundCrowdsaleStub is Versionable, IW12Crowdsale, Secondary, Reentran

function setParameters(uint price) external {}

function updatePurchaseFeeParameterForPaymentMethod(bytes32 method, bool has, uint value) public {}

function getPurchaseFeeParameterForPaymentMethod(bytes32 method) public view returns (bool, uint) {}

function getPurchaseFeeForPaymentMethod(bytes32 method) public view returns (uint) {}

function setup(
uint[6][] parametersOfStages,
uint[] bonusConditionsOfStages,
Expand Down
Loading

0 comments on commit cbb71aa

Please sign in to comment.