Skip to content

Commit

Permalink
Feature/tokensale modify (#188)
Browse files Browse the repository at this point in the history
* mumbai config

* env settings

* modify lib

* participation modify logic

* linking lib

* handle overlock

* check overlock

* removed commented libs

* fixed bug

* moved settings initializing to functions

* modify

* tests

* coverage

* interface

* refactor

* Update contracts/libs/gov/token-sale-proposal/TokenSaleProposalCreate.sol

Co-authored-by: Artem Chystiakov <47551140+Arvolear@users.noreply.github.com>

* tests fix

* flashloan attack

---------

Co-authored-by: Artem Chystiakov <47551140+Arvolear@users.noreply.github.com>
  • Loading branch information
todesstille and Arvolear authored Mar 18, 2024
1 parent ff63c62 commit 4208a7f
Show file tree
Hide file tree
Showing 13 changed files with 986 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ COINMARKETCAP_KEY = "COINMARKETCAP API KEY"
TYPECHAIN_TARGET = "TYPECHAIN TARGET"

# Environment
ENVIRONMENT = "PROD|STAGE|DEV|DEV_SEPOLIA"
ENVIRONMENT = "PROD|STAGE|DEV|DEV_SEPOLIA|DEV_MUMBAI"
30 changes: 30 additions & 0 deletions contracts/gov/proposals/TokenSaleProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ contract TokenSaleProposal is
address saleToken,
ParticipationDetails[] participationDetails
);
event TierModified(
uint256 tierId,
address saleToken,
ParticipationDetails[] participationDetails
);
event Bought(uint256 tierId, address paidWith, uint256 received, uint256 given, address buyer);
event Whitelisted(uint256 tierId, address user);

Expand Down Expand Up @@ -92,6 +97,25 @@ contract TokenSaleProposal is
}
}

function modifyTier(
uint256 tierId,
ITokenSaleProposal.TierInitParams calldata newSettings
) external onlyGov {
_getActiveTier(tierId).modifyTier(newSettings);

emit TierModified(tierId, newSettings.saleTokenAddress, newSettings.participationDetails);
}

function changeParticipationDetails(
uint256 tierId,
ITokenSaleProposal.ParticipationDetails[] calldata newSettings
) external onlyGov {
ITokenSaleProposal.Tier storage tier = _getActiveTier(tierId);
tier.changeParticipationDetails(newSettings);

emit TierModified(tierId, tier.tierInitParams.saleTokenAddress, newSettings);
}

function addToWhitelist(WhitelistingRequest[] calldata requests) external override onlyGov {
for (uint256 i = 0; i < requests.length; i++) {
_getActiveTier(requests[i].tierId).addToWhitelist(requests[i]);
Expand Down Expand Up @@ -221,6 +245,12 @@ contract TokenSaleProposal is
return _tiers.getTierViews(offset, limit);
}

function getParticipationDetails(
uint256 tierId
) external view returns (ITokenSaleProposal.ParticipationInfoView memory) {
return _getActiveTier(tierId).getParticipationDetails();
}

function getUserViews(
address user,
uint256[] calldata tierIds,
Expand Down
44 changes: 44 additions & 0 deletions contracts/interfaces/gov/proposals/ITokenSaleProposal.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,11 @@ interface ITokenSaleProposal {
/// @notice Tier additional parameters
/// @param merkleRoot root of Merkle Tree for whitelist (zero if Merkle proofs turned off)
/// @param merkleUri merkle whitlist uri
/// @param lastModified proposal was last modified on this block number. 0 for the new tiers
struct TierAdditionalInfo {
bytes32 merkleRoot;
string merkleUri;
uint256 lastModified;
}

/// @notice Purchase parameters
Expand Down Expand Up @@ -193,6 +195,28 @@ interface ITokenSaleProposal {
EnumerableMap.AddressToUintMap requiredNftLock;
}

/// @notice Commplete list of participation parameters
/// @param isWhitelisted the boolean indicating whether the tier requires whitelist
/// @param isBABTed the boolean indicating whether the tier requires BABT token
/// @param requiredDaoVotes the required amount of DAO votes
/// @param requiredTokenAddresses list of required tokens to lock
/// @param requiredTokenAmounts list of required amounts of token to lock
/// @param requiredNftAddresses list of required nfts to lock
/// @param requiredNftAmounts list of required amounts of nft to lock
/// @param merkleRoot root of Merkle Tree for whitelist (zero if Merkle proofs turned off)
/// @param merkleUri merkle whitlist uri
struct ParticipationInfoView {
bool isWhitelisted;
bool isBABTed;
uint256 requiredDaoVotes;
address[] requiredTokenAddresses;
uint256[] requiredTokenAmounts;
address[] requiredNftAddresses;
uint256[] requiredNftAmounts;
bytes32 merkleRoot;
string merkleUri;
}

/// @notice User parameters
/// @param purchaseInfo the information about the user purchase
/// @param vestingUserInfo the information about the user vesting
Expand Down Expand Up @@ -254,6 +278,19 @@ interface ITokenSaleProposal {
/// @param tiers parameters of tiers
function createTiers(TierInitParams[] calldata tiers) external;

/// @notice This function is used to modify tier
/// @param tierId the id of tier
/// @param newSettings the new tier settings
function modifyTier(uint256 tierId, TierInitParams calldata newSettings) external;

/// @notice This function is used for changing participation settings of the tier
/// @param tierId id of the tier to modify
/// @param newSettings list of participation parameters to set
function changeParticipationDetails(
uint256 tierId,
ParticipationDetails[] calldata newSettings
) external;

/// @notice This function is used to add users to the whitelist of tier
/// @param requests requests for adding users to the whitelist
function addToWhitelist(WhitelistingRequest[] calldata requests) external;
Expand Down Expand Up @@ -375,6 +412,13 @@ interface ITokenSaleProposal {
uint256 limit
) external view returns (TierView[] memory tierViews);

/// @notice This function is used to get participation settings of a tier
/// @param tierId the tier id
/// @return tierParticipationDetails the list of tier participation settings
function getParticipationDetails(
uint256 tierId
) external view returns (ParticipationInfoView memory tierParticipationDetails);

/// @notice This function is used to get user's infos from tiers
/// @param user the address of the user whose infos are required
/// @param tierIds the list of tier ids to get infos from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,9 +169,10 @@ library TokenSaleProposalBuy {
bool isMerkleRootPresent = tier.tierAdditionalInfo.merkleRoot != bytes32(0);

if (_canParticipate && (participationInfo.isWhitelisted || isMerkleRootPresent)) {
_canParticipate =
tokenSaleProposal.balanceOf(user, tierId) > 0 ||
_checkMerkleProofs(tier, proof, user);
bool turnedOnAndWhitelisted = participationInfo.isWhitelisted &&
tokenSaleProposal.balanceOf(user, tierId) > 0;

_canParticipate = turnedOnAndWhitelisted || _checkMerkleProofs(tier, proof, user);
}

if (_canParticipate && participationInfo.isBABTed) {
Expand Down
201 changes: 173 additions & 28 deletions contracts/libs/gov/token-sale-proposal/TokenSaleProposalCreate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,34 +28,13 @@ library TokenSaleProposalCreate {

ITokenSaleProposal.Tier storage tier = tiers[newTierId];

_setParticipationInfo(tier, _tierInitParams);
_setParticipationInfo(tier, _tierInitParams.participationDetails);
_setRates(tier, _tierInitParams);

uint64 vestingStartTime = _tierInitParams.vestingSettings.vestingDuration == 0
? 0
: _tierInitParams.saleEndTime + _tierInitParams.vestingSettings.cliffPeriod;
tier.tierInfo.vestingTierInfo = ITokenSaleProposal.VestingTierInfo({
vestingStartTime: vestingStartTime,
vestingEndTime: vestingStartTime + _tierInitParams.vestingSettings.vestingDuration
});
_setVestingParameters(tier, _tierInitParams);

ITokenSaleProposal.TierInitParams storage tierInitParams = tier.tierInitParams;

tierInitParams.metadata = _tierInitParams.metadata;
tierInitParams.totalTokenProvided = _tierInitParams.totalTokenProvided;
tierInitParams.saleStartTime = _tierInitParams.saleStartTime;
tierInitParams.saleEndTime = _tierInitParams.saleEndTime;
tierInitParams.claimLockDuration = _tierInitParams.claimLockDuration;
tierInitParams.saleTokenAddress = _tierInitParams.saleTokenAddress;
tierInitParams.purchaseTokenAddresses = _tierInitParams.purchaseTokenAddresses;
tierInitParams.exchangeRates = _tierInitParams.exchangeRates;
tierInitParams.minAllocationPerUser = _tierInitParams.minAllocationPerUser;
tierInitParams.maxAllocationPerUser = _tierInitParams.maxAllocationPerUser;
tierInitParams.vestingSettings = _tierInitParams.vestingSettings;

for (uint256 i = 0; i < _tierInitParams.participationDetails.length; i++) {
tierInitParams.participationDetails.push(_tierInitParams.participationDetails[i]);
}
_setBasicParameters(tierInitParams, _tierInitParams);

IERC20(tierInitParams.saleTokenAddress).safeTransferFrom(
msg.sender,
Expand All @@ -64,6 +43,61 @@ library TokenSaleProposalCreate {
);
}

function modifyTier(
ITokenSaleProposal.Tier storage tier,
ITokenSaleProposal.TierInitParams calldata newSettings
) external {
require(
block.timestamp < tier.tierInitParams.saleStartTime,
"TSP: token sale already started"
);
require(
newSettings.saleTokenAddress == tier.tierInitParams.saleTokenAddress,
"TSP: can't change sale token"
);

ITokenSaleProposal.TierInitParams storage tierInitParams = tier.tierInitParams;

_validateTierInitParams(newSettings);

_clearTierData(tier);

_setParticipationInfo(tier, newSettings.participationDetails);
_setRates(tier, newSettings);
_setVestingParameters(tier, newSettings);

uint256 oldSupply = tierInitParams.totalTokenProvided;
uint256 newSupply = newSettings.totalTokenProvided;

_setBasicParameters(tierInitParams, newSettings);

if (oldSupply < newSupply) {
IERC20(newSettings.saleTokenAddress).safeTransferFrom(
msg.sender,
address(this),
(newSupply - oldSupply).from18Safe(newSettings.saleTokenAddress)
);
} else if (oldSupply > newSupply) {
IERC20(newSettings.saleTokenAddress).safeTransfer(
msg.sender,
(oldSupply - newSupply).from18Safe(newSettings.saleTokenAddress)
);
}
}

function changeParticipationDetails(
ITokenSaleProposal.Tier storage tier,
ITokenSaleProposal.ParticipationDetails[] calldata newSettings
) external {
require(block.timestamp <= tier.tierInitParams.saleEndTime, "TSP: token sale is over");

_clearParticipationData(tier);

_setParticipationInfo(tier, newSettings);

tier.tierAdditionalInfo.lastModified = _getBlockNumber();
}

function getTierViews(
mapping(uint256 => ITokenSaleProposal.Tier) storage tiers,
uint256 offset,
Expand All @@ -86,15 +120,85 @@ library TokenSaleProposalCreate {
}
}

function getParticipationDetails(
ITokenSaleProposal.Tier storage tier
)
external
view
returns (ITokenSaleProposal.ParticipationInfoView memory participationDetails)
{
ITokenSaleProposal.ParticipationInfo storage participationInfo = tier.participationInfo;
ITokenSaleProposal.TierAdditionalInfo storage additionalInfo = tier.tierAdditionalInfo;

participationDetails.isWhitelisted = participationInfo.isWhitelisted;
participationDetails.isBABTed = participationInfo.isBABTed;
participationDetails.requiredDaoVotes = participationInfo.requiredDaoVotes;
participationDetails.merkleRoot = additionalInfo.merkleRoot;
participationDetails.merkleUri = additionalInfo.merkleUri;

uint256 length = participationInfo.requiredTokenLock.length();

address[] memory addresses = new address[](length);
uint256[] memory amounts = new uint256[](length);

for (uint256 i = 0; i < length; i++) {
(addresses[i], amounts[i]) = participationInfo.requiredTokenLock.at(i);
}

participationDetails.requiredTokenAddresses = addresses;
participationDetails.requiredTokenAmounts = amounts;

length = participationInfo.requiredNftLock.length();
addresses = new address[](length);
amounts = new uint256[](length);

for (uint256 i = 0; i < length; i++) {
(addresses[i], amounts[i]) = participationInfo.requiredNftLock.at(i);
}

participationDetails.requiredNftAddresses = addresses;
participationDetails.requiredNftAmounts = amounts;
}

function _setBasicParameters(
ITokenSaleProposal.TierInitParams storage tierInitParams,
ITokenSaleProposal.TierInitParams memory _tierInitParams
) private {
tierInitParams.metadata = _tierInitParams.metadata;
tierInitParams.totalTokenProvided = _tierInitParams.totalTokenProvided;
tierInitParams.saleStartTime = _tierInitParams.saleStartTime;
tierInitParams.saleEndTime = _tierInitParams.saleEndTime;
tierInitParams.claimLockDuration = _tierInitParams.claimLockDuration;
tierInitParams.saleTokenAddress = _tierInitParams.saleTokenAddress;
tierInitParams.purchaseTokenAddresses = _tierInitParams.purchaseTokenAddresses;
tierInitParams.exchangeRates = _tierInitParams.exchangeRates;
tierInitParams.minAllocationPerUser = _tierInitParams.minAllocationPerUser;
tierInitParams.maxAllocationPerUser = _tierInitParams.maxAllocationPerUser;
tierInitParams.vestingSettings = _tierInitParams.vestingSettings;
}

function _setVestingParameters(
ITokenSaleProposal.Tier storage tier,
ITokenSaleProposal.TierInitParams memory _tierInitParams
) private {
uint64 vestingStartTime = _tierInitParams.vestingSettings.vestingDuration == 0
? 0
: _tierInitParams.saleEndTime + _tierInitParams.vestingSettings.cliffPeriod;
tier.tierInfo.vestingTierInfo = ITokenSaleProposal.VestingTierInfo({
vestingStartTime: vestingStartTime,
vestingEndTime: vestingStartTime + _tierInitParams.vestingSettings.vestingDuration
});
}

function _setParticipationInfo(
ITokenSaleProposal.Tier storage tier,
ITokenSaleProposal.TierInitParams memory tierInitParams
ITokenSaleProposal.ParticipationDetails[] memory participationSettings
) private {
ITokenSaleProposal.ParticipationInfo storage participationInfo = tier.participationInfo;

for (uint256 i = 0; i < tierInitParams.participationDetails.length; i++) {
ITokenSaleProposal.ParticipationDetails memory participationDetails = tierInitParams
.participationDetails[i];
for (uint256 i = 0; i < participationSettings.length; i++) {
ITokenSaleProposal.ParticipationDetails
memory participationDetails = participationSettings[i];

if (
participationDetails.participationType ==
Expand Down Expand Up @@ -183,6 +287,8 @@ library TokenSaleProposalCreate {
additionalInfo.merkleRoot = merkleRoot;
additionalInfo.merkleUri = merkleUri;
}

tier.tierInitParams.participationDetails.push(participationDetails);
}
}

Expand All @@ -205,6 +311,34 @@ library TokenSaleProposalCreate {
}
}

function _clearTierData(ITokenSaleProposal.Tier storage tier) private {
for (uint256 i = 0; i < tier.tierInitParams.purchaseTokenAddresses.length; i++) {
tier.rates[tier.tierInitParams.purchaseTokenAddresses[i]] = 0;
}

_clearParticipationData(tier);
}

function _clearParticipationData(ITokenSaleProposal.Tier storage tier) private {
ITokenSaleProposal.ParticipationInfo storage participationInfo = tier.participationInfo;
ITokenSaleProposal.TierAdditionalInfo storage additionalInfo = tier.tierAdditionalInfo;

participationInfo.isWhitelisted = false;
participationInfo.isBABTed = false;
participationInfo.requiredDaoVotes = 0;
_clearEnumerableMap(participationInfo.requiredTokenLock);
_clearEnumerableMap(participationInfo.requiredNftLock);
additionalInfo.merkleRoot = bytes32(0);
additionalInfo.merkleUri = "";

ITokenSaleProposal.ParticipationDetails[] storage participationDetails = tier
.tierInitParams
.participationDetails;
assembly {
sstore(participationDetails.slot, 0)
}
}

function _validateTierInitParams(
ITokenSaleProposal.TierInitParams memory tierInitParams
) private pure {
Expand Down Expand Up @@ -259,4 +393,15 @@ library TokenSaleProposalCreate {
vestingSettings.vestingPercentage <= PERCENTAGE_100 &&
vestingSettings.vestingDuration >= vestingSettings.unlockStep;
}

function _clearEnumerableMap(EnumerableMap.AddressToUintMap storage map) internal {
for (uint256 i = map.length(); i > 0; i--) {
(address key, ) = map.at(i - 1);
map.remove(key);
}
}

function _getBlockNumber() internal view returns (uint256 block_) {
return block.number;
}
}
Loading

0 comments on commit 4208a7f

Please sign in to comment.