From b35dbb9283a07facd857cd3989ba91f9add1a288 Mon Sep 17 00:00:00 2001 From: ennucore Date: Thu, 29 Dec 2022 13:55:17 +0100 Subject: [PATCH] Pricing --- contracts/imports/dns-utils.fc | 31 ++++++++ contracts/main.ts | 131 ++++++++++++++++++--------------- contracts/nft-collection.fc | 10 ++- test/creation.spec.ts | 14 +++- 4 files changed, 123 insertions(+), 63 deletions(-) diff --git a/contracts/imports/dns-utils.fc b/contracts/imports/dns-utils.fc index 2314bac..abf1f18 100644 --- a/contracts/imports/dns-utils.fc +++ b/contracts/imports/dns-utils.fc @@ -172,6 +172,37 @@ int check_domain_string(slice domain) { return (10, 1); } +int price_function(int length, int multiplierx10, int steepness) { + ;; length is the length of the domain, the price depends on it. The rest is the price config + ;; multiplierx10 is the price multiplier, the default price is multiplied by multiplierx10 / 10 + ;; steepness (from 0 to 10) is the steepness of the price function, + ;; 10 means the distribution is as in the function from `get_min_price_config` (the second number), 0 means the price is always the same, 50 + int price = 50; + if (length == 3) { + price = 200; + } + if (length == 4) { + price = 100; + } + if (length == 5) { + price = 50; + } + if (length == 6) { + price = 40; + } + if (length == 7) { + price = 30; + } + if (length == 8) { + price = 20; + } + if (length >= 9) { + price = 10; + } + price = (steepness * price + (10 - steepness) * 50) / 10; + return price * one_ton * multiplierx10 / 10; +} + int get_min_price(int domain_bits_length, int now_time) { (int start_min_price, int end_min_price) = get_min_price_config(domain_bits_length / 8); start_min_price *= one_ton; diff --git a/contracts/main.ts b/contracts/main.ts index 1ad948c..0a8fb0c 100644 --- a/contracts/main.ts +++ b/contracts/main.ts @@ -1,16 +1,16 @@ import BN from "bn.js"; -import { Cell, beginCell, Address } from "ton"; -import { C7Config, SmartContract } from "ton-contract-executor"; +import {Cell, beginCell, Address} from "ton"; +import {C7Config, SmartContract} from "ton-contract-executor"; import {encodeOffChainContent, makeSnakeCell} from "./utils"; -import { randomBytes } from "crypto"; -import { keyPairFromSeed, KeyPair, sign } from "ton-crypto"; -import { ExpansionPanelActions } from "@material-ui/core"; +import {randomBytes} from "crypto"; +import {keyPairFromSeed, KeyPair, sign} from "ton-crypto"; +import {ExpansionPanelActions} from "@material-ui/core"; // encode contract storage according to save_data() contract method export function genKeyPair(): KeyPair { - let seed = randomBytes(32); - return keyPairFromSeed(seed); + let seed = randomBytes(32); + return keyPairFromSeed(seed); } // nft_item_code:^Cell @@ -22,89 +22,100 @@ export function genKeyPair(): KeyPair { // cell auction - auction info // int64 last_fill_up_time export function data(params: { ownerAddress: Address; collectionAddress: Address, code: Cell, domain: String, publicKey: Buffer }): Cell { - let data_cell = beginCell() - // For code: https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-auction/build.sh - .storeRef(params.code) - .storeUint(0, 256) - .storeAddress(params.collectionAddress) - .storeAddress(params.ownerAddress) - .storeRef(encodeOffChainContent("https://agorata.io/collection.json")) // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md - .storeRef(makeSnakeCell(Buffer.from(params.domain))) - .storeDict(null) - .storeUint(0, 64).endCell(); + let data_cell = beginCell() + // For code: https://github.com/getgems-io/nft-contracts/blob/main/packages/contracts/sources/nft-auction/build.sh + .storeRef(params.code) + .storeUint(0, 256) + .storeAddress(params.collectionAddress) + .storeAddress(params.ownerAddress) + .storeRef(encodeOffChainContent("https://agorata.io/collection.json")) // https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md + .storeRef(makeSnakeCell(Buffer.from(params.domain))) + .storeDict(null) + .storeUint(0, 64).endCell(); - return beginCell().storeRef(data_cell).storeBuffer(params.publicKey).endCell(); + return beginCell().storeRef(data_cell).storeBuffer(params.publicKey).endCell(); } -export function collectionData(params: { code: Cell, ownerAddress: Address, ownerKey: number }): Cell { +export function collectionData(params: { + code: Cell, ownerAddress: Address, ownerKey: number, + price_multiplier?: number, price_steepness?: number +}): Cell { + if (params.price_multiplier == undefined) { + params.price_multiplier = 10; + } + if (params.price_steepness == undefined) { + params.price_steepness = 5; + } return beginCell() .storeRef(encodeOffChainContent("https://agorata.io/collection.json")) .storeRef(params.code) - .storeRef(beginCell().endCell()) + .storeRef(beginCell().storeUint(params.price_multiplier, 8).storeUint(params.price_steepness, 4).endCell()) .storeUint(params.ownerKey, 256) .storeAddress(params.ownerAddress) - .endCell(); + .endCell(); } export function auctionWithWinner(winnerAddress: Address) { - return beginCell().storeAddress(winnerAddress).storeCoins(0).storeUint(0, 64) + return beginCell().storeAddress(winnerAddress).storeCoins(0).storeUint(0, 64) } export function setContractBalance(contract: SmartContract, balance: number) { - contract.setC7Config({balance: balance}); + contract.setC7Config({balance: balance}); } -export function TON(): number { return 1000000000; } +export function TON(): number { + return 1000000000; +} // message encoders for all ops (see contracts/imports/constants.fc for consts) export function transferOwnership(params: { newOwnerAddress: Address }): Cell { - return beginCell().storeUint(0x5fcc3d14, 32).storeUint(0, 64).storeAddress(params.newOwnerAddress).storeAddress(null).storeInt(0, 1).storeCoins(1 * TON()).endCell(); + return beginCell().storeUint(0x5fcc3d14, 32).storeUint(0, 64).storeAddress(params.newOwnerAddress).storeAddress(null).storeInt(0, 1).storeCoins(1 * TON()).endCell(); } export function createItem(params: { domain: String }): Cell { - let signature = '000'; - return beginCell() - .storeUint(0, 32) - .storeRef(makeSnakeCell(Buffer.from(params.domain + ';' + signature))) - .endCell(); + let signature = '000'; + return beginCell() + .storeUint(0, 32) + .storeRef(makeSnakeCell(Buffer.from(params.domain + ';' + signature))) + .endCell(); } export function instantBuySignature(receiverAddress: Address, issuedCollectionAddr: Address, amount: number, domain: Cell, privateKey: Buffer): Buffer { - let messageToSign = beginCell().storeAddress(receiverAddress).storeAddress(issuedCollectionAddr).storeUint(amount, 256).storeRef(domain).endCell(); - let hash = messageToSign.hash(); - // console.log(hash.toString("hex")); - // console.log((new BN(hash)).toString(10)) - // return sign(hash, privateKey); - let althash = new BN("FF11841721E4DAD5AE679A1A338B2EBFC5AAF7529C200B4EF9D71831B1DCB969", "hex"); - return sign(althash.toBuffer(), privateKey); + let messageToSign = beginCell().storeAddress(receiverAddress).storeAddress(issuedCollectionAddr).storeUint(amount, 256).storeRef(domain).endCell(); + let hash = messageToSign.hash(); + // console.log(hash.toString("hex")); + // console.log((new BN(hash)).toString(10)) + // return sign(hash, privateKey); + let althash = new BN("FF11841721E4DAD5AE679A1A338B2EBFC5AAF7529C200B4EF9D71831B1DCB969", "hex"); + return sign(althash.toBuffer(), privateKey); } -export function instantBuyMessage(params: { receiverAddress: Address, issuedCollectionAddr: Address, price: number, domain: String, privateKey: Buffer}): Cell { - let domainSnakeCell = makeSnakeCell(Buffer.from(params.domain)); - let signature = instantBuySignature(params.receiverAddress, params.issuedCollectionAddr, params.price, domainSnakeCell, params.privateKey); - console.log(signature.toString("hex").toUpperCase()); - let cell = beginCell() - .storeUint(0x16c7d435, 32) // opcode - .storeUint(0, 64) // query id - .storeBuffer(signature) // body - .storeUint(params.price, 256) - .storeRef(domainSnakeCell).endCell(); - return cell; +export function instantBuyMessage(params: { receiverAddress: Address, issuedCollectionAddr: Address, price: number, domain: String, privateKey: Buffer }): Cell { + let domainSnakeCell = makeSnakeCell(Buffer.from(params.domain)); + let signature = instantBuySignature(params.receiverAddress, params.issuedCollectionAddr, params.price, domainSnakeCell, params.privateKey); + console.log(signature.toString("hex").toUpperCase()); + let cell = beginCell() + .storeUint(0x16c7d435, 32) // opcode + .storeUint(0, 64) // query id + .storeBuffer(signature) // body + .storeUint(params.price, 256) + .storeRef(domainSnakeCell).endCell(); + return cell; } export function currentState(contract: SmartContract) { - let c4 = contract.dataCell.beginParse(); - let reader = c4.readRef(); + let c4 = contract.dataCell.beginParse(); + let reader = c4.readRef(); - return { - nft_item_code: reader.readRef(), - index: reader.readUint(256), - collectionAddress: reader.readAddress(), - ownerAddress: reader.readAddress(), - collectionContent: reader.readRef(), - domain: reader.readRef(), - // auction: reader.readCell(), – TODO: still havent's figured out to load auction here - // lastFillUpTime: reader.readInt(64) - } + return { + nft_item_code: reader.readRef(), + index: reader.readUint(256), + collectionAddress: reader.readAddress(), + ownerAddress: reader.readAddress(), + collectionContent: reader.readRef(), + domain: reader.readRef(), + // auction: reader.readCell(), – TODO: still havent's figured out to load auction here + // lastFillUpTime: reader.readInt(64) + } } diff --git a/contracts/nft-collection.fc b/contracts/nft-collection.fc index b9c1138..359a29b 100644 --- a/contracts/nft-collection.fc +++ b/contracts/nft-collection.fc @@ -41,7 +41,10 @@ int calcprice(slice domain, cell pricing) inline_ref { throw_unless(201, len <= 126 * 8); ;; maxmimum 126 characters throw_unless(202, mod(len, 8) == 0); throw_unless(203, check_domain_string(domain)); - return 100000; ;; todo + slice pr = pricing.begin_parse(); + int multiplier = pr~load_uint(8); + int steepness = pr~load_uint(4); + return price_function(len / 8, multiplier, steepness); } cell calculate_nft_item_state_init(int item_index, cell nft_item_code) { @@ -138,6 +141,11 @@ cell get_nft_content(int index, cell individual_nft_content) method_id { return individual_nft_content; } +int get_price(slice domain) method_id { + var (content, nft_item_code, pricing, key, addr) = load_data(); + return calcprice(domain, pricing); +} + (int, cell) dnsresolve(slice subdomain, int category) method_id { throw_unless(70, mod(slice_bits(subdomain), 8) == 0); diff --git a/test/creation.spec.ts b/test/creation.spec.ts index afe9a46..277c50c 100644 --- a/test/creation.spec.ts +++ b/test/creation.spec.ts @@ -33,13 +33,23 @@ describe("Creating items tests", () => { const sendToSelfMessage = internalMessage({ from: ownerAddr, body: main.createItem({ domain: "test" }), - value: new BN(10 * main.TON()), + value: new BN(100 * main.TON()), }); const res = await contract.sendInternalMessage(sendToSelfMessage); console.log(res); expect(res.type).to.equal("success"); expect(res.exit_code).to.equal(0); - + }); + it("does not allow to buy an item if the price is too low", async () => { + main.setContractBalance(contract, 10 * main.TON()); + let ownerAddr = randomAddress("owner"); + const sendToSelfMessage = internalMessage({ + from: ownerAddr, + body: main.createItem({ domain: "test" }), + value: new BN(10 * main.TON()), + }); + const res = await contract.sendInternalMessage(sendToSelfMessage); + expect(res.type).to.equal("failed"); }); });