@gala-chain/launchpad 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/package.json +1 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts +3 -1
- package/lib/src/api/types/LaunchpadDtos.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadDtos.js +19 -1
- package/lib/src/api/types/LaunchpadDtos.js.map +1 -1
- package/lib/src/api/types/LaunchpadSale.d.ts +8 -1
- package/lib/src/api/types/LaunchpadSale.d.ts.map +1 -1
- package/lib/src/api/types/LaunchpadSale.js +37 -8
- package/lib/src/api/types/LaunchpadSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/buyWithNative.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/buyWithNative.js +4 -1
- package/lib/src/chaincode/launchpad/buyWithNative.js.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenIn.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenIn.js +6 -4
- package/lib/src/chaincode/launchpad/callMemeTokenIn.js.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenOut.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callMemeTokenOut.js +19 -8
- package/lib/src/chaincode/launchpad/callMemeTokenOut.js.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenIn.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenIn.js +13 -6
- package/lib/src/chaincode/launchpad/callNativeTokenIn.js.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenOut.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/callNativeTokenOut.js +6 -4
- package/lib/src/chaincode/launchpad/callNativeTokenOut.js.map +1 -1
- package/lib/src/chaincode/launchpad/createSale.d.ts.map +1 -1
- package/lib/src/chaincode/launchpad/createSale.js +22 -6
- package/lib/src/chaincode/launchpad/createSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/finaliseSale.js +2 -2
- package/lib/src/chaincode/launchpad/finaliseSale.js.map +1 -1
- package/lib/src/chaincode/launchpad/sellExactToken.js +1 -1
- package/lib/src/chaincode/launchpad/sellExactToken.js.map +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.d.ts.map +1 -1
- package/lib/src/chaincode/utils/launchpadSaleUtils.js +14 -2
- package/lib/src/chaincode/utils/launchpadSaleUtils.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/api/types/LaunchpadDtos.ts +22 -1
- package/src/api/types/LaunchpadSale.ts +51 -8
- package/src/chaincode/launchpad/buyExactToken.spec.ts +234 -1
- package/src/chaincode/launchpad/buyWithNative.spec.ts +163 -8
- package/src/chaincode/launchpad/buyWithNative.ts +5 -1
- package/src/chaincode/launchpad/callMemeTokenIn.ts +11 -4
- package/src/chaincode/launchpad/callMemeTokenOut.spec.ts +1 -1
- package/src/chaincode/launchpad/callMemeTokenOut.ts +24 -8
- package/src/chaincode/launchpad/callNativeTokenIn.ts +18 -7
- package/src/chaincode/launchpad/callNativeTokenOut.ts +10 -4
- package/src/chaincode/launchpad/createSale.ts +35 -7
- package/src/chaincode/launchpad/finaliseSale.ts +2 -2
- package/src/chaincode/launchpad/sellExactToken.spec.ts +139 -2
- package/src/chaincode/launchpad/sellExactToken.ts +1 -1
- package/src/chaincode/launchpad/sellWithNative.spec.ts +157 -1
- package/src/chaincode/utils/launchpadSaleUtils.ts +18 -2
|
@@ -29,15 +29,26 @@ function calculateTokensPurchasable(
|
|
|
29
29
|
nativeTokens: Decimal,
|
|
30
30
|
totalTokensSold: Decimal,
|
|
31
31
|
nativeTokenDecimals: number,
|
|
32
|
-
sellingTokenDecimals: number
|
|
32
|
+
sellingTokenDecimals: number,
|
|
33
|
+
adjustableSupplyMultiplier?: number
|
|
33
34
|
): [string, string] {
|
|
34
|
-
const basePrice =
|
|
35
|
-
|
|
35
|
+
const basePrice =
|
|
36
|
+
adjustableSupplyMultiplier && adjustableSupplyMultiplier > 0
|
|
37
|
+
? new Decimal(LaunchpadSale.BASE_PRICE).dividedBy(adjustableSupplyMultiplier)
|
|
38
|
+
: new Decimal(LaunchpadSale.BASE_PRICE);
|
|
39
|
+
|
|
40
|
+
const { exponentFactor, euler, decimals } = getBondingConstants(adjustableSupplyMultiplier);
|
|
36
41
|
|
|
37
42
|
// Round native tokens, then calculate tokens based on that rounded amount
|
|
38
43
|
const roundedNativeTokens = nativeTokens.toDecimalPlaces(nativeTokenDecimals, Decimal.ROUND_UP);
|
|
39
44
|
|
|
40
|
-
// Calculate tokens purchasable:
|
|
45
|
+
// Calculate tokens purchasable:
|
|
46
|
+
// newTokens = (decimals / exponentFactor) *
|
|
47
|
+
// ln(
|
|
48
|
+
// (nativeTokens * exponentFactor / basePrice) +
|
|
49
|
+
// e^(exponentFactor * totalTokensSold / decimals)
|
|
50
|
+
// ) -
|
|
51
|
+
// totalTokensSold
|
|
41
52
|
// Where:
|
|
42
53
|
// constant = nativeTokens * exponentFactor / basePrice
|
|
43
54
|
// exponent1 = exponentFactor * totalTokensSold / decimals
|
|
@@ -55,9 +66,13 @@ function calculateTokensPurchasable(
|
|
|
55
66
|
const result = lnEthScaledBase.minus(totalTokensSold);
|
|
56
67
|
let roundedResult = result.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_DOWN);
|
|
57
68
|
|
|
58
|
-
// Cap total supply
|
|
59
|
-
|
|
60
|
-
|
|
69
|
+
// Cap total supply
|
|
70
|
+
const supplyCap = adjustableSupplyMultiplier
|
|
71
|
+
? new BigNumber(1e7).times(adjustableSupplyMultiplier).toString()
|
|
72
|
+
: "1e+7";
|
|
73
|
+
|
|
74
|
+
if (roundedResult.add(totalTokensSold).greaterThan(new Decimal(supplyCap))) {
|
|
75
|
+
roundedResult = new Decimal(supplyCap).minus(new Decimal(totalTokensSold));
|
|
61
76
|
}
|
|
62
77
|
|
|
63
78
|
return [roundedNativeTokens.toFixed(), roundedResult.toFixed()];
|
|
@@ -119,7 +134,8 @@ export async function callMemeTokenOut(
|
|
|
119
134
|
nativeTokens,
|
|
120
135
|
totalTokensSold,
|
|
121
136
|
nativeTokenDecimals,
|
|
122
|
-
sellingTokenDecimals
|
|
137
|
+
sellingTokenDecimals,
|
|
138
|
+
sale?.adjustableSupplyMultiplier
|
|
123
139
|
);
|
|
124
140
|
|
|
125
141
|
// Fetch fee configuration and return result
|
|
@@ -29,15 +29,25 @@ function calculateNativeTokensRequired(
|
|
|
29
29
|
tokensToBuy: Decimal,
|
|
30
30
|
totalTokensSold: Decimal,
|
|
31
31
|
sellingTokenDecimals: number,
|
|
32
|
-
nativeTokenDecimals: number
|
|
32
|
+
nativeTokenDecimals: number,
|
|
33
|
+
adjustableSupplyMultiplier?: number
|
|
33
34
|
): [string, string] {
|
|
34
|
-
const basePrice =
|
|
35
|
-
|
|
35
|
+
const basePrice =
|
|
36
|
+
adjustableSupplyMultiplier && adjustableSupplyMultiplier > 0
|
|
37
|
+
? new Decimal(LaunchpadSale.BASE_PRICE).dividedBy(adjustableSupplyMultiplier)
|
|
38
|
+
: new Decimal(LaunchpadSale.BASE_PRICE);
|
|
39
|
+
|
|
40
|
+
const { exponentFactor, euler, decimals } = getBondingConstants(adjustableSupplyMultiplier);
|
|
36
41
|
|
|
37
42
|
// Round tokens first, then calculate native tokens based on that rounded amount
|
|
38
43
|
const roundedTokensToBuy = tokensToBuy.toDecimalPlaces(sellingTokenDecimals, Decimal.ROUND_DOWN);
|
|
39
44
|
|
|
40
|
-
// Calculate native tokens required:
|
|
45
|
+
// Calculate native tokens required:
|
|
46
|
+
// price = (basePrice / exponentFactor) *
|
|
47
|
+
// (
|
|
48
|
+
// e^(exponentFactor * (totalTokensSold + tokensToBuy) / decimals) -
|
|
49
|
+
// e^(exponentFactor * totalTokensSold / decimals)
|
|
50
|
+
// )
|
|
41
51
|
// Where:
|
|
42
52
|
// exponent1 = exponentFactor * (totalTokensSold + tokensToBuy) / decimals
|
|
43
53
|
// exponent2 = exponentFactor * totalTokensSold / decimals
|
|
@@ -81,13 +91,13 @@ export async function callNativeTokenIn(
|
|
|
81
91
|
ctx: GalaChainContext,
|
|
82
92
|
buyTokenDTO: ExactTokenQuantityDto
|
|
83
93
|
): Promise<TradeCalculationResDto> {
|
|
84
|
-
const sale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
|
|
94
|
+
const sale: LaunchpadSale = await fetchAndValidateSale(ctx, buyTokenDTO.vaultAddress);
|
|
85
95
|
const totalTokensSold = new Decimal(sale.fetchTokensSold());
|
|
86
96
|
|
|
87
97
|
let tokensToBuy = new Decimal(buyTokenDTO.tokenQuantity.toString());
|
|
88
98
|
|
|
89
99
|
// Adjust tokensToBuy if user is trying to buy more tokens than the total supply
|
|
90
|
-
if (tokensToBuy.add(totalTokensSold).greaterThan(new Decimal(
|
|
100
|
+
if (tokensToBuy.add(totalTokensSold).greaterThan(new Decimal(sale.maxSupply.toString()))) {
|
|
91
101
|
tokensToBuy = new Decimal(sale.sellingTokenQuantity);
|
|
92
102
|
}
|
|
93
103
|
|
|
@@ -99,7 +109,8 @@ export async function callNativeTokenIn(
|
|
|
99
109
|
tokensToBuy,
|
|
100
110
|
totalTokensSold,
|
|
101
111
|
sellingTokenDecimals,
|
|
102
|
-
nativeTokenDecimals
|
|
112
|
+
nativeTokenDecimals,
|
|
113
|
+
sale.adjustableSupplyMultiplier
|
|
103
114
|
);
|
|
104
115
|
|
|
105
116
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
@@ -29,13 +29,18 @@ function calculateNativeTokensReceived(
|
|
|
29
29
|
sale: LaunchpadSale,
|
|
30
30
|
tokensToSellBn: BigNumber,
|
|
31
31
|
sellingTokenDecimals: number,
|
|
32
|
-
nativeTokenDecimals: number
|
|
32
|
+
nativeTokenDecimals: number,
|
|
33
|
+
adjustableSupplyMultiplier?: number
|
|
33
34
|
): [string, string] {
|
|
34
35
|
const totalTokensSold = new Decimal(sale.fetchTokensSold());
|
|
35
36
|
|
|
36
37
|
let tokensToSell = new Decimal(tokensToSellBn.toString());
|
|
37
|
-
const basePrice =
|
|
38
|
-
|
|
38
|
+
const basePrice =
|
|
39
|
+
adjustableSupplyMultiplier && adjustableSupplyMultiplier > 0
|
|
40
|
+
? new Decimal(LaunchpadSale.BASE_PRICE).dividedBy(adjustableSupplyMultiplier)
|
|
41
|
+
: new Decimal(LaunchpadSale.BASE_PRICE);
|
|
42
|
+
|
|
43
|
+
const { exponentFactor, euler, decimals } = getBondingConstants(adjustableSupplyMultiplier);
|
|
39
44
|
|
|
40
45
|
let newTotalTokensSold = totalTokensSold.minus(tokensToSell);
|
|
41
46
|
|
|
@@ -88,7 +93,8 @@ export async function callNativeTokenOut(
|
|
|
88
93
|
sale,
|
|
89
94
|
sellTokenDTO.tokenQuantity,
|
|
90
95
|
sellingTokenDecimals,
|
|
91
|
-
nativeTokenDecimals
|
|
96
|
+
nativeTokenDecimals,
|
|
97
|
+
sale.adjustableSupplyMultiplier
|
|
92
98
|
);
|
|
93
99
|
const launchpadFeeAddressConfiguration = await fetchLaunchpadFeeAddress(ctx);
|
|
94
100
|
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* See the License for the specific language governing permissions and
|
|
13
13
|
* limitations under the License.
|
|
14
14
|
*/
|
|
15
|
-
import { ConflictError, TokenInstanceKey, asValidUserAlias } from "@gala-chain/api";
|
|
15
|
+
import { ConflictError, DefaultError, TokenInstanceKey, asValidUserAlias } from "@gala-chain/api";
|
|
16
16
|
import {
|
|
17
17
|
GalaChainContext,
|
|
18
18
|
createTokenClass,
|
|
@@ -23,7 +23,13 @@ import {
|
|
|
23
23
|
} from "@gala-chain/chaincode";
|
|
24
24
|
import BigNumber from "bignumber.js";
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
CreateSaleResDto,
|
|
28
|
+
CreateTokenSaleDTO,
|
|
29
|
+
LaunchpadSale,
|
|
30
|
+
NativeTokenQuantityDto,
|
|
31
|
+
SaleStatus
|
|
32
|
+
} from "../../api/types";
|
|
27
33
|
import { PreConditionFailedError } from "../../api/utils/error";
|
|
28
34
|
import { buyWithNative } from "./buyWithNative";
|
|
29
35
|
|
|
@@ -80,6 +86,8 @@ export async function createSale(
|
|
|
80
86
|
throw new ConflictError("This token and a sale associated with it already exists");
|
|
81
87
|
}
|
|
82
88
|
|
|
89
|
+
const supplyCapMultiplier = launchpadDetails.adjustableSupplyMultiplier ?? 1;
|
|
90
|
+
|
|
83
91
|
// Call createTokenClass
|
|
84
92
|
await createTokenClass(ctx, {
|
|
85
93
|
network: "GC",
|
|
@@ -90,8 +98,8 @@ export async function createSale(
|
|
|
90
98
|
symbol: launchpadDetails.tokenSymbol,
|
|
91
99
|
description: launchpadDetails.tokenDescription,
|
|
92
100
|
image: launchpadDetails.tokenImage,
|
|
93
|
-
maxSupply: new BigNumber("2e+7"),
|
|
94
|
-
maxCapacity: new BigNumber("2e+7"),
|
|
101
|
+
maxSupply: new BigNumber("2e+7").times(supplyCapMultiplier),
|
|
102
|
+
maxCapacity: new BigNumber("2e+7").times(supplyCapMultiplier),
|
|
95
103
|
totalMintAllowance: new BigNumber(0),
|
|
96
104
|
totalSupply: new BigNumber(0),
|
|
97
105
|
totalBurned: new BigNumber(0),
|
|
@@ -103,12 +111,13 @@ export async function createSale(
|
|
|
103
111
|
tokenClassKey: tokenInstanceKey.getTokenClassKey(),
|
|
104
112
|
tokenInstance: new BigNumber(0),
|
|
105
113
|
owner: vaultAddress,
|
|
106
|
-
quantity: new BigNumber("2e+7")
|
|
114
|
+
quantity: new BigNumber("2e+7").times(supplyCapMultiplier)
|
|
107
115
|
});
|
|
108
116
|
|
|
109
|
-
//Update token class to remove the calling user as an authority in the token class
|
|
117
|
+
// Update token class to remove the calling user as an authority in the token class
|
|
110
118
|
await updateTokenClass(ctx, {
|
|
111
119
|
tokenClass: tokenInstanceKey.getTokenClassKey(),
|
|
120
|
+
overwriteAuthorities: true,
|
|
112
121
|
authorities: [vaultAddress]
|
|
113
122
|
});
|
|
114
123
|
|
|
@@ -117,7 +126,9 @@ export async function createSale(
|
|
|
117
126
|
vaultAddress,
|
|
118
127
|
tokenInstanceKey,
|
|
119
128
|
launchpadDetails.reverseBondingCurveConfiguration?.toChainObject(),
|
|
120
|
-
ctx.callingUser
|
|
129
|
+
ctx.callingUser,
|
|
130
|
+
undefined,
|
|
131
|
+
launchpadDetails.adjustableSupplyMultiplier
|
|
121
132
|
);
|
|
122
133
|
|
|
123
134
|
await putChainObject(ctx, launchpad);
|
|
@@ -131,6 +142,23 @@ export async function createSale(
|
|
|
131
142
|
isSaleFinalized = tradeStatus.isFinalized;
|
|
132
143
|
}
|
|
133
144
|
|
|
145
|
+
// handling the optional saleStartTime after the preBuyQuantity allows
|
|
146
|
+
// creators to still optionally specify a pre-buy even when a
|
|
147
|
+
// sale is marked Upcoming / Coming Soon.
|
|
148
|
+
// Otherwise `buyWithNative` would throw an error when validating the sale is active.
|
|
149
|
+
if (launchpadDetails.saleStartTime !== undefined) {
|
|
150
|
+
launchpad.saleStartTime = launchpadDetails.saleStartTime;
|
|
151
|
+
|
|
152
|
+
// handle edge case
|
|
153
|
+
// if a sale was immediately sold out with a pre-buy on creation,
|
|
154
|
+
// do not set the ended sale back to UPCOMING
|
|
155
|
+
if (launchpad.saleStatus !== SaleStatus.END) {
|
|
156
|
+
launchpad.saleStatus = SaleStatus.UPCOMING;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await putChainObject(ctx, launchpad);
|
|
160
|
+
}
|
|
161
|
+
|
|
134
162
|
// Return the response object
|
|
135
163
|
return {
|
|
136
164
|
image: launchpadDetails.tokenImage,
|
|
@@ -203,8 +203,8 @@ function calculateFinalLaunchpadPrice(
|
|
|
203
203
|
areTokensSorted: boolean
|
|
204
204
|
): { sqrtPrice: BigNumber; finalPrice: BigNumber } {
|
|
205
205
|
const totalTokensSold = new Decimal(sale.fetchTokensSold());
|
|
206
|
-
const basePrice = new Decimal(
|
|
207
|
-
const { exponentFactor, euler, decimals } = getBondingConstants();
|
|
206
|
+
const basePrice = new Decimal(sale.basePrice.toString());
|
|
207
|
+
const { exponentFactor, euler, decimals } = getBondingConstants(sale.adjustableSupplyMultiplier);
|
|
208
208
|
|
|
209
209
|
const exponent = exponentFactor.mul(totalTokensSold).div(decimals);
|
|
210
210
|
const multiplicand = euler.pow(exponent);
|
|
@@ -21,11 +21,11 @@ import {
|
|
|
21
21
|
asValidUserAlias,
|
|
22
22
|
randomUniqueKey
|
|
23
23
|
} from "@gala-chain/api";
|
|
24
|
-
import { currency, fixture, users } from "@gala-chain/test";
|
|
24
|
+
import { currency, fixture, transactionSuccess, users } from "@gala-chain/test";
|
|
25
25
|
import BigNumber from "bignumber.js";
|
|
26
26
|
import { plainToInstance } from "class-transformer";
|
|
27
27
|
|
|
28
|
-
import { ExactTokenQuantityDto, LaunchpadSale } from "../../api/types";
|
|
28
|
+
import { ExactTokenQuantityDto, LaunchpadSale, TradeResDto } from "../../api/types";
|
|
29
29
|
import { LaunchpadContract } from "../LaunchpadContract";
|
|
30
30
|
import launchpadgala from "../test/launchpadgala";
|
|
31
31
|
|
|
@@ -204,4 +204,141 @@ describe("sellExactToken", () => {
|
|
|
204
204
|
expect(response.Data?.inputQuantity).toBe("500");
|
|
205
205
|
expect(new BigNumber(response.Data?.outputQuantity || "0").isPositive()).toBe(true);
|
|
206
206
|
});
|
|
207
|
+
|
|
208
|
+
let galaPurchaseQtyDefaultSupply: BigNumber;
|
|
209
|
+
|
|
210
|
+
test("Adjustable supply: Single transaction yields expected value for default 10 Million supply", async () => {
|
|
211
|
+
// Given
|
|
212
|
+
const multiplier = undefined;
|
|
213
|
+
|
|
214
|
+
sale = new LaunchpadSale(
|
|
215
|
+
vaultAddress,
|
|
216
|
+
currencyInstance.instanceKeyObj(),
|
|
217
|
+
undefined,
|
|
218
|
+
users.testUser1.identityKey,
|
|
219
|
+
undefined,
|
|
220
|
+
multiplier
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
224
|
+
.registeredUsers(users.testUser1)
|
|
225
|
+
.savedState(
|
|
226
|
+
currencyClass,
|
|
227
|
+
currencyInstance,
|
|
228
|
+
launchpadGalaClass,
|
|
229
|
+
launchpadGalaInstance,
|
|
230
|
+
sale,
|
|
231
|
+
salelaunchpadGalaBalance,
|
|
232
|
+
saleCurrencyBalance,
|
|
233
|
+
userlaunchpadGalaBalance,
|
|
234
|
+
userCurrencyBalance
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const buyDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500"));
|
|
238
|
+
|
|
239
|
+
buyDto.uniqueKey = randomUniqueKey();
|
|
240
|
+
buyDto.sign(users.testUser1.privateKey);
|
|
241
|
+
|
|
242
|
+
const expectedBuyResponse = plainToInstance(TradeResDto, {
|
|
243
|
+
inputQuantity: "0.00825575",
|
|
244
|
+
totalFees: "0",
|
|
245
|
+
totalTokenSold: new BigNumber("500").toString(),
|
|
246
|
+
outputQuantity: new BigNumber("500").toString(),
|
|
247
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
248
|
+
tradeType: "Buy",
|
|
249
|
+
uniqueKey: buyDto.uniqueKey,
|
|
250
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
251
|
+
userAddress: "client|testUser1",
|
|
252
|
+
isFinalized: false,
|
|
253
|
+
functionName: "BuyExactToken"
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
const sellDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("50"));
|
|
257
|
+
sellDto.uniqueKey = randomUniqueKey();
|
|
258
|
+
const signedDto = sellDto.signed(users.testUser1.privateKey);
|
|
259
|
+
|
|
260
|
+
// When
|
|
261
|
+
const buyRes = await contract.BuyExactToken(ctx, buyDto);
|
|
262
|
+
|
|
263
|
+
const sellRes = await contract.SellExactToken(ctx, signedDto);
|
|
264
|
+
|
|
265
|
+
// Then
|
|
266
|
+
expect(buyRes).toEqual(transactionSuccess(expectedBuyResponse));
|
|
267
|
+
|
|
268
|
+
expect(sellRes.Status).toBe(1);
|
|
269
|
+
expect(sellRes.Data?.inputQuantity).toBe("50");
|
|
270
|
+
expect(sellRes.Data?.outputQuantity).toBe("0.00082579");
|
|
271
|
+
|
|
272
|
+
galaPurchaseQtyDefaultSupply = new BigNumber(sellRes.Data?.outputQuantity ?? 0);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test("Adjustable supply: Single transaction yields expected quantity for 100x scaled 1 Billion supply", async () => {
|
|
276
|
+
// Given
|
|
277
|
+
const multiplier = 100;
|
|
278
|
+
const inputQty = new BigNumber("50").times(multiplier);
|
|
279
|
+
|
|
280
|
+
sale = new LaunchpadSale(
|
|
281
|
+
vaultAddress,
|
|
282
|
+
currencyInstance.instanceKeyObj(),
|
|
283
|
+
undefined,
|
|
284
|
+
users.testUser1.identityKey,
|
|
285
|
+
undefined,
|
|
286
|
+
multiplier
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
290
|
+
.registeredUsers(users.testUser1)
|
|
291
|
+
.savedState(
|
|
292
|
+
currencyClass,
|
|
293
|
+
currencyInstance,
|
|
294
|
+
launchpadGalaClass,
|
|
295
|
+
launchpadGalaInstance,
|
|
296
|
+
sale,
|
|
297
|
+
salelaunchpadGalaBalance,
|
|
298
|
+
saleCurrencyBalance,
|
|
299
|
+
userlaunchpadGalaBalance,
|
|
300
|
+
userCurrencyBalance
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
const buyDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500").times(multiplier));
|
|
304
|
+
|
|
305
|
+
buyDto.uniqueKey = randomUniqueKey();
|
|
306
|
+
buyDto.sign(users.testUser1.privateKey);
|
|
307
|
+
|
|
308
|
+
const expectedBuyResponse = plainToInstance(TradeResDto, {
|
|
309
|
+
inputQuantity: "0.00825575",
|
|
310
|
+
totalFees: "0",
|
|
311
|
+
totalTokenSold: new BigNumber("500").times(multiplier).toString(),
|
|
312
|
+
outputQuantity: new BigNumber("500").times(multiplier).toString(),
|
|
313
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
314
|
+
tradeType: "Buy",
|
|
315
|
+
uniqueKey: buyDto.uniqueKey,
|
|
316
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
317
|
+
userAddress: "client|testUser1",
|
|
318
|
+
isFinalized: false,
|
|
319
|
+
functionName: "BuyExactToken"
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
const sellDto = new ExactTokenQuantityDto(vaultAddress, inputQty);
|
|
323
|
+
sellDto.uniqueKey = randomUniqueKey();
|
|
324
|
+
const signedDto = sellDto.signed(users.testUser1.privateKey);
|
|
325
|
+
|
|
326
|
+
// When
|
|
327
|
+
const buyRes = await contract.BuyExactToken(ctx, buyDto);
|
|
328
|
+
|
|
329
|
+
const sellRes = await contract.SellExactToken(ctx, signedDto);
|
|
330
|
+
|
|
331
|
+
// Then
|
|
332
|
+
expect(buyRes).toEqual(transactionSuccess(expectedBuyResponse));
|
|
333
|
+
|
|
334
|
+
expect(sellRes.Status).toBe(1);
|
|
335
|
+
expect(sellRes.Data?.inputQuantity).toBe(inputQty.toString());
|
|
336
|
+
expect(sellRes.Data?.outputQuantity).toBe("0.00082579");
|
|
337
|
+
|
|
338
|
+
const galaPurchaseQty100xSupply = new BigNumber(sellRes.Data?.outputQuantity ?? -1);
|
|
339
|
+
|
|
340
|
+
// Compared to the previous test where the Launchpad has the default 10 Million supply,
|
|
341
|
+
// We expect the Meme token Qty to scale 100x and the Gala Qty to remain the same
|
|
342
|
+
expect(galaPurchaseQtyDefaultSupply).toEqual(galaPurchaseQty100xSupply);
|
|
343
|
+
});
|
|
207
344
|
});
|
|
@@ -58,7 +58,7 @@ export async function sellExactToken(
|
|
|
58
58
|
const memeToken = sale.fetchSellingTokenInstanceKey();
|
|
59
59
|
|
|
60
60
|
// Abort if the vault doesn't have enough native tokens to pay the user
|
|
61
|
-
if (
|
|
61
|
+
if (nativeTokensPayout.isGreaterThan(nativeTokensLeftInVault)) {
|
|
62
62
|
throw new ValidationFailedError("Not enough GALA in sale contract to carry out this operation.");
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -26,7 +26,7 @@ import { currency, fixture, transactionError, transactionSuccess, users } from "
|
|
|
26
26
|
import BigNumber from "bignumber.js";
|
|
27
27
|
import { plainToInstance } from "class-transformer";
|
|
28
28
|
|
|
29
|
-
import { LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
|
|
29
|
+
import { ExactTokenQuantityDto, LaunchpadSale, NativeTokenQuantityDto, TradeResDto } from "../../api/types";
|
|
30
30
|
import { LaunchpadContract } from "../LaunchpadContract";
|
|
31
31
|
import launchpadgala from "../test/launchpadgala";
|
|
32
32
|
|
|
@@ -286,4 +286,160 @@ describe("sellWithNative", () => {
|
|
|
286
286
|
|
|
287
287
|
// todo: check writes map, verify vault balance
|
|
288
288
|
});
|
|
289
|
+
|
|
290
|
+
let galaPurchaseQtyDefaultSupply: BigNumber;
|
|
291
|
+
let memeSaleQtyDefaultSupply: BigNumber;
|
|
292
|
+
|
|
293
|
+
test("Adjustable supply: Single transaction yields expected value for default 10 Million supply", async () => {
|
|
294
|
+
// Given
|
|
295
|
+
const multiplier = undefined;
|
|
296
|
+
galaPurchaseQtyDefaultSupply = new BigNumber("0.00082579");
|
|
297
|
+
memeSaleQtyDefaultSupply = new BigNumber("49.999949130655");
|
|
298
|
+
|
|
299
|
+
sale = new LaunchpadSale(
|
|
300
|
+
vaultAddress,
|
|
301
|
+
currencyInstance.instanceKeyObj(),
|
|
302
|
+
undefined,
|
|
303
|
+
users.testUser1.identityKey,
|
|
304
|
+
undefined,
|
|
305
|
+
multiplier
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
309
|
+
.registeredUsers(users.testUser1)
|
|
310
|
+
.savedState(
|
|
311
|
+
currencyClass,
|
|
312
|
+
currencyInstance,
|
|
313
|
+
launchpadGalaClass,
|
|
314
|
+
launchpadGalaInstance,
|
|
315
|
+
sale,
|
|
316
|
+
salelaunchpadGalaBalance,
|
|
317
|
+
saleCurrencyBalance,
|
|
318
|
+
userlaunchpadGalaBalance,
|
|
319
|
+
userCurrencyBalance
|
|
320
|
+
);
|
|
321
|
+
|
|
322
|
+
const buyDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500"));
|
|
323
|
+
|
|
324
|
+
buyDto.uniqueKey = randomUniqueKey();
|
|
325
|
+
buyDto.sign(users.testUser1.privateKey);
|
|
326
|
+
|
|
327
|
+
const expectedBuyResponse = plainToInstance(TradeResDto, {
|
|
328
|
+
inputQuantity: "0.00825575",
|
|
329
|
+
totalFees: "0",
|
|
330
|
+
totalTokenSold: new BigNumber("500").toString(),
|
|
331
|
+
outputQuantity: new BigNumber("500").toString(),
|
|
332
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
333
|
+
tradeType: "Buy",
|
|
334
|
+
uniqueKey: buyDto.uniqueKey,
|
|
335
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
336
|
+
userAddress: "client|testUser1",
|
|
337
|
+
isFinalized: false,
|
|
338
|
+
functionName: "BuyExactToken"
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const sellDto = new NativeTokenQuantityDto(vaultAddress, galaPurchaseQtyDefaultSupply);
|
|
342
|
+
sellDto.uniqueKey = randomUniqueKey();
|
|
343
|
+
const signedDto = sellDto.signed(users.testUser1.privateKey);
|
|
344
|
+
|
|
345
|
+
// When
|
|
346
|
+
const buyRes = await contract.BuyExactToken(ctx, buyDto);
|
|
347
|
+
|
|
348
|
+
const sellRes = await contract.SellWithNative(ctx, signedDto);
|
|
349
|
+
|
|
350
|
+
// Then
|
|
351
|
+
expect(buyRes).toEqual(transactionSuccess(expectedBuyResponse));
|
|
352
|
+
|
|
353
|
+
expect(sellRes).toEqual(
|
|
354
|
+
transactionSuccess(
|
|
355
|
+
expect.objectContaining({
|
|
356
|
+
outputQuantity: galaPurchaseQtyDefaultSupply.toString(),
|
|
357
|
+
// extra precision in set constant above accounts for loss of precision
|
|
358
|
+
// when increased by the multiplier below.
|
|
359
|
+
// here, we round to the token decimal places to match the internal logic
|
|
360
|
+
inputQuantity: memeSaleQtyDefaultSupply.decimalPlaces(10).toString()
|
|
361
|
+
})
|
|
362
|
+
)
|
|
363
|
+
);
|
|
364
|
+
galaPurchaseQtyDefaultSupply = new BigNumber(sellRes.Data?.outputQuantity ?? 0);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test("Adjustable supply: Single transaction yields expected quantity for 100x scaled 1 Billion supply", async () => {
|
|
368
|
+
// Given
|
|
369
|
+
const multiplier = 100;
|
|
370
|
+
|
|
371
|
+
// Same Gala purchase amount should buy 100x meme token output
|
|
372
|
+
const inputQty = new BigNumber(galaPurchaseQtyDefaultSupply);
|
|
373
|
+
|
|
374
|
+
sale = new LaunchpadSale(
|
|
375
|
+
vaultAddress,
|
|
376
|
+
currencyInstance.instanceKeyObj(),
|
|
377
|
+
undefined,
|
|
378
|
+
users.testUser1.identityKey,
|
|
379
|
+
undefined,
|
|
380
|
+
multiplier
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const { ctx, contract } = fixture(LaunchpadContract)
|
|
384
|
+
.registeredUsers(users.testUser1)
|
|
385
|
+
.savedState(
|
|
386
|
+
currencyClass,
|
|
387
|
+
currencyInstance,
|
|
388
|
+
launchpadGalaClass,
|
|
389
|
+
launchpadGalaInstance,
|
|
390
|
+
sale,
|
|
391
|
+
salelaunchpadGalaBalance,
|
|
392
|
+
saleCurrencyBalance,
|
|
393
|
+
userlaunchpadGalaBalance,
|
|
394
|
+
userCurrencyBalance
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
const buyDto = new ExactTokenQuantityDto(vaultAddress, new BigNumber("500").times(multiplier));
|
|
398
|
+
|
|
399
|
+
buyDto.uniqueKey = randomUniqueKey();
|
|
400
|
+
buyDto.sign(users.testUser1.privateKey);
|
|
401
|
+
|
|
402
|
+
const expectedBuyResponse = plainToInstance(TradeResDto, {
|
|
403
|
+
inputQuantity: "0.00825575",
|
|
404
|
+
totalFees: "0",
|
|
405
|
+
totalTokenSold: new BigNumber("500").times(multiplier).toString(),
|
|
406
|
+
outputQuantity: new BigNumber("500").times(multiplier).toString(),
|
|
407
|
+
tokenName: "AUTOMATEDTESTCOIN",
|
|
408
|
+
tradeType: "Buy",
|
|
409
|
+
uniqueKey: buyDto.uniqueKey,
|
|
410
|
+
vaultAddress: "service|GALA$Unit$none$none$launchpad",
|
|
411
|
+
userAddress: "client|testUser1",
|
|
412
|
+
isFinalized: false,
|
|
413
|
+
functionName: "BuyExactToken"
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
const sellDto = new NativeTokenQuantityDto(vaultAddress, inputQty);
|
|
417
|
+
sellDto.uniqueKey = randomUniqueKey();
|
|
418
|
+
const signedDto = sellDto.signed(users.testUser1.privateKey);
|
|
419
|
+
|
|
420
|
+
// When
|
|
421
|
+
const buyRes = await contract.BuyExactToken(ctx, buyDto);
|
|
422
|
+
|
|
423
|
+
const sellRes = await contract.SellWithNative(ctx, signedDto);
|
|
424
|
+
|
|
425
|
+
// Then
|
|
426
|
+
expect(buyRes).toEqual(transactionSuccess(expectedBuyResponse));
|
|
427
|
+
|
|
428
|
+
expect(sellRes).toEqual(
|
|
429
|
+
transactionSuccess(
|
|
430
|
+
expect.objectContaining({
|
|
431
|
+
inputQuantity: memeSaleQtyDefaultSupply.times(multiplier).toString(),
|
|
432
|
+
outputQuantity: galaPurchaseQtyDefaultSupply.toString()
|
|
433
|
+
})
|
|
434
|
+
)
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
const galaPurchaseQty100xSupply = new BigNumber(sellRes.Data?.outputQuantity ?? -1);
|
|
438
|
+
const memeSaleQty100xSupply = new BigNumber(sellRes.Data?.inputQuantity ?? -1);
|
|
439
|
+
|
|
440
|
+
// Compared to the previous test where the Launchpad has the default 10 Million supply,
|
|
441
|
+
// We expect the Meme token Qty to scale 100x and the Gala Qty to remain the same
|
|
442
|
+
expect(galaPurchaseQtyDefaultSupply).toEqual(galaPurchaseQty100xSupply);
|
|
443
|
+
expect(memeSaleQtyDefaultSupply).toEqual(memeSaleQty100xSupply.dividedBy(multiplier));
|
|
444
|
+
});
|
|
289
445
|
});
|
|
@@ -35,6 +35,18 @@ export async function fetchAndValidateSale(
|
|
|
35
35
|
if (sale === undefined) {
|
|
36
36
|
throw new NotFoundError("Sale record not found.");
|
|
37
37
|
}
|
|
38
|
+
|
|
39
|
+
if (sale.saleStatus === SaleStatus.UPCOMING) {
|
|
40
|
+
if (sale.saleStartTime !== undefined && sale.saleStartTime > 0 && sale.saleStartTime < ctx.txUnixTime) {
|
|
41
|
+
sale.saleStatus = SaleStatus.ONGOING;
|
|
42
|
+
} else {
|
|
43
|
+
throw new DefaultError(
|
|
44
|
+
`Upcoming: This sale is coming soon. ` +
|
|
45
|
+
`${sale.saleStartTime ? "Sale starts at: " + sale.saleStartTime + " Unix time." : "Start time TBD."}`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
38
50
|
if (sale.saleStatus === SaleStatus.END) {
|
|
39
51
|
throw new DefaultError("This sale has already ended.");
|
|
40
52
|
}
|
|
@@ -42,9 +54,13 @@ export async function fetchAndValidateSale(
|
|
|
42
54
|
return sale;
|
|
43
55
|
}
|
|
44
56
|
|
|
45
|
-
export function getBondingConstants() {
|
|
57
|
+
export function getBondingConstants(multiplier?: number) {
|
|
58
|
+
const exponentFactor =
|
|
59
|
+
multiplier && multiplier > 0
|
|
60
|
+
? new Decimal("1166069000000").times(1 / multiplier)
|
|
61
|
+
: new Decimal("1166069000000");
|
|
46
62
|
return {
|
|
47
|
-
exponentFactor:
|
|
63
|
+
exponentFactor: exponentFactor, // exponent factor / b
|
|
48
64
|
euler: new Decimal("2.7182818284590452353602874713527"), // e
|
|
49
65
|
decimals: new Decimal("1e+18") // scaling factor for decimals
|
|
50
66
|
};
|