Browse Source

dev: selling

dev/signature-instant-buy
igor 2 years ago
parent
commit
86256c1adc
  1. 4
      .gitignore
  2. 3
      contracts/imports/op-codes.fc
  3. 84
      contracts/main.fc
  4. 59
      contracts/main.ts
  5. 1150
      package-lock.json
  6. 2
      package.json
  7. 2
      test/ownership.spec.ts
  8. 56
      test/selling.spec.ts

4
.gitignore vendored

@ -0,0 +1,4 @@
node_modules
.vscode
contracts/dns-contract
.gitmodules

3
contracts/imports/op-codes.fc

@ -17,7 +17,8 @@ int op::editorship_assigned() asm "0x511a4463 PUSHINT";
;; Collection
int op::new_nft() asm "0x1a039a51 PUSHINT";
int op::instant_buy_new_nft() asm "16c7d435 PUSHINT";
int op::instant_buy_new_nft() asm "0x16c7d435 PUSHINT";
int op::init_after_buy() asm "0x437dc408 PUSHINT";
;; DNS
const int op::fill_up = 0x370fec51;

84
contracts/main.fc

@ -52,11 +52,19 @@ cell pack_auction(slice max_bid_address, int max_bid_amount, int auction_end_tim
.end_cell();
}
int get_public_key() method_id {
var cs = get_data().begin_parse();
cs~load_ref();
return cs.preload_uint(256);
}
;; cell content, cell nft_item_code, uint256 index, address collection_address,
;; address owner_address, cell domain, cell auction, int last_fill_up_time
;;
;; cell: cell content, cell nft_item_code, uint256 index, address collection_address,
;; address owner_address, cell domain, cell auction, int last_fill_up_time
;; uint256 public_key
(cell, cell, int, int, slice, slice, cell, cell, int) load_data() {
slice ds = get_data().begin_parse();
slice root_cell = get_data().begin_parse();
slice ds = root_cell~load_ref().begin_parse();
cell code = ds~load_ref(); ;; code
var (index, collection_address) = (ds~load_uint(256), ds~load_msg_addr());
if (ds.slice_bits() > 0) {
@ -105,20 +113,10 @@ cell pack_state(cell content, cell nft_item_code, int index, slice collection_ad
;; Serialize the data using the storage schema
() store_data(cell content, cell nft_item_code, int index, slice collection_address, slice owner_address, cell domain, cell auction, int last_fill_up_time) impure {
;; set_data(
;; begin_cell()
;; .store_ref(nft_item_code)
;; .store_uint(index, 256)
;; .store_slice(collection_address)
;; .store_slice(owner_address)
;; .store_ref(content)
;; .store_ref(domain)
;; .store_dict(auction)
;; .store_uint(last_fill_up_time, 64)
;; .end_cell()
;; );
set_data(pack_state(content, nft_item_code, index, collection_address, owner_address, domain, auction, last_fill_up_time));
cell data = pack_state(content, nft_item_code, index, collection_address, owner_address, domain, auction, last_fill_up_time);
int public_key = get_public_key();
set_data(begin_cell().store_ref(data).store_uint(public_key, 256).end_cell());
}
() send_msg(slice to_address, int amount, int op, int query_id, builder payload, int send_mode) impure inline {
@ -205,7 +203,7 @@ cell pack_nft_item_state(cell nft_item_code, cell data) impure {
return begin_cell().store_uint(0, 2).store_dict(nft_item_code).store_dict(data).store_uint(0, 1).end_cell();
}
() deploy_bought_item(int item_index, cell code, slice owner_address, slice domain) impure {
() deploy_bought_item(int item_index, cell code, slice owner_address, cell domain) impure {
;; cell nft_item_code
;; uint256 index --- The index of this item in the collection
;; MsgAddressInt collection_address
@ -220,7 +218,7 @@ cell pack_nft_item_state(cell nft_item_code, cell data) impure {
.store_slice(my_address())
.store_slice(owner_address)
.store_dict(null())
.store_ref(begin_cell().store_uint(1, 1).store_slice(domain).end_cell()) ;; TODO: snake cell
.store_ref(domain) ;; snake cell
.store_dict(null())
.store_uint(0, 64)
.end_cell();
@ -233,7 +231,8 @@ cell pack_nft_item_state(cell nft_item_code, cell data) impure {
.store_coins(0)
.store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)
.store_ref(state_init)
.store_uint(1, 1); ;; the content of the NFT item, will be treated as the parameter of the first incoming message
.store_uint(1, 1)
.store_ref(begin_cell().store_uint(0x437dc408, 32).end_cell()); ;; the content of the NFT item, will be treated as the parameter of the first incoming message
send_raw_message(msg.end_cell(), 64); ;; carry all the remaining value of the inbound message, fee deducted from amount
}
@ -314,32 +313,30 @@ cell pack_nft_item_state(cell nft_item_code, cell data) impure {
store_data(content, item_code, index, collection_address, owner_address, domain, auction, last_fill_up_time);
}
if (op == op::init_after_buy()) {
return ();
}
if (op == op::instant_buy_new_nft()) {
;; parsing of the signed 'option'
;; signature structure: (receiver_addr, collection_address, )
cell option_data = in_msg_body~load_ref();
cell signature = in_msg_body~load_ref();
slice reader = option_data.begin_parse();
slice receiver_addr = reader~load_msg_addr();
slice issued_collection_addr = reader~load_msg_addr();
int amount = reader~load_uint(256);
slice new_domain = reader;
throw_unless(411, slice_bits(new_domain) > 0);
throw_unless(412, equal_slices(receiver_addr, sender_address)); ;; TODO: Unsure here
throw_unless(413, equal_slices(issued_collection_addr, my_address()));
int success = check_data_signature(option_data.begin_parse(), signature.begin_parse(), owner_address);
throw_unless(413, success);
throw_unless(414, msg_value > amount);
amount_to_send = msg_value - amount; ;; TODO: Handle this later, reroute coins
int new_item_index = slice_hash(new_domain);
deploy_bought_item(item_index, cell item_code, slice receiver_addr, slice new_domain);
;; signature structure: (receiver_addr, issued_collection_address, amount, domain)
slice signature = in_msg_body~load_bits(512);
int amount = in_msg_body~load_uint(256);
slice issued_collection_addr = my_address();
cell new_domain = in_msg_body~load_ref();
int public_key = get_public_key();
cell signed_data = begin_cell().store_slice(sender_address).store_slice(issued_collection_addr).store_uint(amount, 256).store_ref(new_domain).end_cell();
int hash = cell_hash(signed_data);
dump_stack();
int success? = check_signature(hash, signature, public_key);
throw_unless(413, success?);
throw_unless(414, msg_value >= amount);
int amount_to_send = msg_value - amount; ;; TODO: Handle this later, reroute coins
int new_item_index = cell_hash(new_domain);
deploy_bought_item(new_item_index, item_code, sender_address, new_domain);
return ();
;; init_state = pack_state(... add domain here ...)
;; use modified function `deploy_nft_item` <- init_state
}
if (op == op::new_nft()) {
throw_unless(401, equal_slices(sender_address, owner_address));
@ -356,7 +353,6 @@ cell pack_nft_item_state(cell nft_item_code, cell data) impure {
store_data(in_msg_body~load_ref(), item_code, index, collection_address, owner_address, domain, auction, now());
return ();
}
if (op == op::get_static_data()) {
send_msg(sender_address, 0, op::report_static_data(), query_id, begin_cell().store_uint(index, 256).store_slice(collection_address), 64); ;; carry all the remaining value of the inbound message
return ();

59
contracts/main.ts

@ -2,9 +2,17 @@ import BN from "bn.js";
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";
// encode contract storage according to save_data() contract method
export function genKeyPair(): KeyPair {
let seed = randomBytes(32);
return keyPairFromSeed(seed);
}
// nft_item_code:^Cell
// uint256 index
// MsgAddressInt collection_address
@ -13,17 +21,19 @@ import {encodeOffChainContent, makeSnakeCell} from "./utils";
// cell domain - e.g contains "alice" (without ending \0) for "alice.ton" domain
// cell auction - auction info
// int64 last_fill_up_time
export function data(params: { ownerAddress: Address; collectionAddress: Address, code: Cell, domain: String }): Cell {
return 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();
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();
return beginCell().storeRef(data_cell).storeBuffer(params.publicKey).endCell();
}
export function auctionWithWinner(winnerAddress: Address) {
@ -42,8 +52,33 @@ 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();
}
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);
}
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 reader = contract.dataCell.beginParse();
let c4 = contract.dataCell.beginParse();
let reader = c4.readRef();
return {
nft_item_code: reader.readRef(),
index: reader.readUint(256),

1150
package-lock.json generated

File diff suppressed because it is too large Load Diff

2
package.json

@ -26,7 +26,7 @@
"mocha": "^9.1.3",
"prando": "^6.0.1",
"prettier": "^2.6.2",
"ton": "9.9.0",
"ton": "^12.3.3",
"ton-contract-executor": "^0.4.8",
"ton-crypto": "^3.1.0",
"ts-node": "^10.4.0",

2
test/ownership.spec.ts

@ -13,6 +13,7 @@ import { hex } from "../build/main.compiled.json";
describe("Transfer ownership tests", () => {
let contract: SmartContract;
let debug: boolean = false;
let keyPair = main.genKeyPair();
beforeEach(async () => {
contract = await SmartContract.fromCell(
@ -22,6 +23,7 @@ describe("Transfer ownership tests", () => {
code: Cell.fromBoc(hex)[0],
collectionAddress: randomAddress("collection"),
domain: "alice",
publicKey: keyPair.publicKey
}),
{ debug: debug }
);

56
test/selling.spec.ts

@ -0,0 +1,56 @@
import chai, { assert, expect } from "chai";
import chaiBN from "chai-bn";
import BN from "bn.js";
chai.use(chaiBN(BN));
import { Builder, Cell, Slice } from "ton";
import { SmartContract } from "ton-contract-executor";
import * as main from "../contracts/main";
import { internalMessage, randomAddress } from "./helpers";
import { hex } from "../build/main.compiled.json";
import { makeSnakeCell } from "../contracts/utils";
import { signVerify } from "ton-crypto";
describe("Selling tests (instant buy)", () => {
let contract: SmartContract;
let debug: boolean = true;
let keyPair = main.genKeyPair();
beforeEach(async () => {
contract = await SmartContract.fromCell(
Cell.fromBoc(hex)[0],
main.data({
ownerAddress: randomAddress("owner"),
code: Cell.fromBoc(hex)[0],
collectionAddress: randomAddress("collection"),
domain: "alice",
publicKey: keyPair.publicKey
}),
{ debug: debug }
);
});
it("Sell", async () => {
main.setContractBalance(contract, 10 * main.TON());
const sellMessage = internalMessage({
from: randomAddress("buyer"),
body: main.instantBuyMessage({
receiverAddress: randomAddress("buyer"),
issuedCollectionAddr: randomAddress("collection"),
price: 10 * main.TON(),
domain: "bob",
privateKey: keyPair.secretKey
}),
value: new BN(10 * main.TON())
});
context("Public key is correct", async () => {
const pubKeyRes = await contract.invokeGetMethod("get_public_key", []);
expect(pubKeyRes.result[0]).to.equal(keyPair.publicKey);
});
const res = await contract.sendInternalMessage(sellMessage);
expect(res.type).to.equal("success");
expect(res.exit_code).to.equal(0);
});
});
Loading…
Cancel
Save