diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3e2f5ee --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/ +node_modules/ +bot/config.js \ No newline at end of file diff --git a/DOGE.addr b/DOGE.addr new file mode 100644 index 0000000..82e76a7 Binary files /dev/null and b/DOGE.addr differ diff --git a/README.md b/README.md index 7fa1cd7..fe63c39 100644 --- a/README.md +++ b/README.md @@ -1 +1,78 @@ -# ton-exchange \ No newline at end of file +# TON Decentralized Exchange + +This is implementation of fully decentralized exchange with the support of the exchange of grams, extra currencies and [TRC20 tokens](https://github.com/cod1ng-studio/TRC20). + +###Compilation + +Contract: `func -P -o dex.fif stdlib.fc msg_hex_comment.fc dex.fc` + +Bot: `npm install` then `node ExchangeBot.js` + +###Internal messages: + +Following the [TON smartcontracts guidelines](https://test.ton.org/smguidelines.txt) all internal messages should start with 32 uint operation_id and 64 uint query_id. + +If the query is invalid, the smart contract should throw an exception. + +After successful completion of the query, the smart contract should respond with a message 0x90000000 + +`` + +Therefore, you need to apply 1 extra gram if you send grams to the contract. + +Unused grams will be return back. + +If operation is unsupported, the smart contract should respond with a message 0xffffffff. + +--- + +insert_order(from_value, from_currency_type, from_currency, to_value, to_currency_type, to_currency) - creates a new exchange order. + +currency_type has 2 values: + +0 - if currency is Gram or build-in TON extra-currency. In this case currency value is 32 signed int (TON currency ID, Gram is 0). + +1 - if currency is TRC20 token. In this case currency value is TRC20 token smart contract address (8bit wc + 256bit addr). + +if from_currency is gram or TON extra currency an (appropriate amount + 1 Gram) must be attached to the message. + +if from_currency is TRC20 token you must make approve the corresponding amount of the token in favor of the exchange before sending the message. + +After someone else creates an order directly opposite to yours, an exchange is made. + +Order lifetime is 1 day. If the order is not closed during the day, the funds will be returned back. + +The funds will automatically go to the wallets from which you created the order. + +There is currently no partial-exchange of orders. + +`fift insert-order.fif 0.33 1 DOGE.addr 0.1 0 0` - creates order 0.33 DOGE -> 0.1 Gram + +--- + +cancel_order(order_id) - cancel exchange order which you created. + +The funds will automatically go to the wallets from which you created the order. + +`fift cancel-order.fif 1` - cancel order with ID 1 + +--- + +###Get methods: + +_ orders(slice sender, slice from_currency, slice to_currency) - list of pending orders. + +If parameter is empty slice it will be ignored, else orders will be filtering by specified parameter(-s). + +`orders "" "" ""` - show all pending orders + +###Bot + +For convenience, you can create queries by sending simple messages with text comment to token smart contract. +In this case, the comment text should contain the hex of the message body (produced by 'csr.' fift command). + +Thanks to this, it was possible to create a user friendly interface - [@gram100_bot](https://t.me/gram100_bot) in Telegram. + +Using the bot, users can exchange currencies without using a console. + +The bot generates ton:// links that you need to open with your TON wallet and send. \ No newline at end of file diff --git a/bot/ExchangeBot.js b/bot/ExchangeBot.js new file mode 100644 index 0000000..f561487 --- /dev/null +++ b/bot/ExchangeBot.js @@ -0,0 +1,488 @@ +var cp = require('child_process'); +const {exec} = require('child_process'); + +const Telegraf = require('telegraf') +const Extra = require('telegraf/extra') +const Markup = require('telegraf/markup') +const session = require('telegraf/session') + +const config = require('./config'); + +const exchangeBot = new Telegraf(config.token) + +exchangeBot.catch((err, ctx) => { + console.error(err); +}); + +const path = config.path; + +const fift = path + 'liteclient-build/crypto/fift -I' + path + 'lite-client/crypto/fift/lib -s'; +const fiftInt = path + 'liteclient-build/crypto/fift'; +const litenode = cmd => path + 'liteclient-build/lite-client/lite-client -C ' + path + 'liteclient-build/ton-lite-client-test1.config.json -c"' + cmd + '"'; + + +const dex_addr = 'kQCjv39FHShgXfqmDrs6fer34HZwtB2FH6KOHF_NysjuzT0C'; + +const currencies = { + 0: { + name: 'Gram', + symbol: '💎', + decimals: 18, + isToken: 0, + } +}; + +const formatName = (addr) => { + const currency = currencies[addr]; + if (!currency) { + return addr; + } + return currency.symbol + ' ' + currency.name; +}; + +const checkAddr = (addr) => new Promise((resolve, reject) => { + exec([fift, 'check-addr.fif', addr].join(' '), (err, stdout, stderr) => { + if (err) { + reject(); + } else { + const valid = stdout.trimRight().endsWith('-1'); + if (valid) { + const arr = stdout.split(' ').map(s => s.trim()).filter(s => s.length > 0); + resolve([valid, arr[2] === '3', arr[0], arr[1]]); + } else { + resolve([false, 0, 0, 0]); + } + } + }); +}); + +const sliceToAddr = (slice) => new Promise((resolve, reject) => { + var child = cp.spawn(fiftInt, ['-i', '-I' + path + 'lite-client/crypto/fift/lib']); + + child.stdin.write('x{' + slice + '} 8 i@+ 256 u@ 0 smca>$ .s'); + + child.stdout.on('data', function (data) { + const arr = format('' + data); + resolve(arr[0].substring(1, arr[0].length - 1)) + }); + child.stdin.end(); +}); + +const sliceToString = (slice) => new Promise((resolve, reject) => { + var child = cp.spawn(fiftInt, ['-i', '-I' + path+ 'lite-client/crypto/fift/lib']); + + child.stdin.write('x{' + slice + '} 4 $@ .s'); + + child.stdout.on('data', function (data) { + const arr = format('' + data); + resolve(arr[0].substring(1, arr[0].length - 1)) + }); + child.stdin.end(); +}); + +const createOrder = (fromValue, fromAddr, toValue, toAddr) => new Promise((resolve, reject) => { + const fromCurrencyType = currencies[fromAddr].isToken; + const toCurrencyType = currencies[toAddr].isToken; + + exec([fift, 'insert-order.fif', fromValue, fromCurrencyType, fromAddr, toValue, toCurrencyType, toAddr].join(' '), (err, stdout, stderr) => { + if (err) { + reject() + } else { + if (stdout.startsWith('x{')) { + resolve(stdout.substring(2, stdout.length - 2)); + } else { + reject() + } + } + }); +}); + +const createApprove = (fromValue) => new Promise((resolve, reject) => { + exec([fift, 'approve.fif', dex_addr, fromValue].join(' '), (err, stdout, stderr) => { + if (err) { + reject() + } else { + if (stdout.startsWith('x{')) { + resolve(stdout.substring(2, stdout.length - 2)); + } else { + reject() + } + } + }); +}); + +const parseSlice = s => s.substring('CSCell'.length).substring(4); + +const format = s => s.trim() + .replace(/bits.*?}/g, '') + .replace(/\[/g, '') + .replace(/]/g, '') + .replace(/\(/g, '') + .replace(/\)/g, '') + .replace(/\{/g, '') + .replace(/}/g, '') + .split(' ') + .filter(s => s.length > 0) + .map(s => s.startsWith('CSCell') ? parseSlice(s) : s); + + +const runMethod = (params) => new Promise((resolve, reject) => { + exec(litenode(params.join(' ')), (err, stdout, stderr) => { + if (err) { + reject(); + } else { + const arr = format(stderr.substring(stderr.indexOf('result:') + 7)); + sliceToString(arr[0]).then(resolve); + } + }); +}); + +const getName = (addr) => runMethod(['runmethod', addr, 'get_name']); +const getSymbol = (addr) => runMethod(['runmethod', addr, 'get_symbol']); +const getDecimals = (addr) => runMethod(['runmethod', addr, 'get_decimals']); + +async function addCurrency(addr) { + if (currencies.hasOwnProperty(addr)) { + return currencies[addr]; + } + + const name = await getName(addr); + const symbol = await getSymbol(addr); + const decimals = 10;//await getDecimals(addr); + currencies[addr] = { + name: name, + symbol: symbol, + decimals: decimals, + isToken: 1 + } +} + +async function getOrderCur(addr) { + if (addr === '00000000') { + return 'Gram'; + } else { + const x = await sliceToAddr(addr); + return await getName(x); + } +} + +function parseGram(value) { + return value / 1000000000; +} + +function toGram(value) { + return value * 1000000000; +} + +async function parseOrders(arr) { + if (arr.length === 0) { + return 'No pending orders'; + } else { + let result = ''; + const n = Math.floor(arr.length / 6); + + for (let i = 0; i < n * 6; i += 6) { + const id = Number(arr[i + 0]); + const paid = Number(arr[i + 1]); + const sender = await sliceToAddr(arr[i + 2]); + const fromValue = parseGram(arr[i + 3]); + const fromCurrency = await getOrderCur(arr[i + 4]); + const toValue = parseGram(arr[i + 5]); + const toCurrency = await getOrderCur(arr[i + 6]); + const orderStr = [id, '-', fromValue, fromCurrency, '->', toValue, toCurrency].join(' ') + '\n'; + result += orderStr; + } + + return 'Pending orders:\n' + result; + } +} + +const getOrders = (ctx) => new Promise((resolve, reject) => { + exec(litenode(['runmethod', dex_addr, 'my_orders', ctx.session.myAddrWc, ctx.session.myAddrI].join(' ')), (err, stdout, stderr) => { + if (err) { + reject(); + } else { + parseOrders(format(stderr.substring(stderr.indexOf('result:') + 7))).then(resolve); + } + }); +}); + +exchangeBot.use(Telegraf.log()) +exchangeBot.use(session()) + +exchangeBot.command('start', ctx => { + ctx.session.counter = 0; + showState(ctx); +}); + +exchangeBot.hears('💰 New Order', ctx => { + ctx.session.counter = 1; + showState(ctx); +}); + +exchangeBot.hears('👛 My Orders', ctx => { + ctx.session.counter = 10; + showState(ctx); +}); + +const acceptAddress = async (ctx, next) => { + const text = ctx.message.text; + + const arr = await checkAddr(text); + if (arr[0]) { + ctx.session.myAddrWc = arr[2]; + ctx.session.myAddrI = arr[3]; + next(); + } else { + ctx.reply('Invalid address, try again'); + } +}; + +const acceptCurrencyType = async (ctx, next) => { + const text = ctx.message.text; + switch (text) { + case '💎 Gram': + next(false); + break; + case '💰 TRC20 Token': + next(true); + break; + } +}; + +const acceptCurrency = async (ctx, next) => { + const text = ctx.message.text; + + const arr = await checkAddr(text); + if (arr[0]) { + if (arr[1]) { + ctx.reply('WARNING: Non-bounceable address'); + } + + await addCurrency(text); + + next(); + } else { + ctx.reply('Invalid address, try again'); + } +}; + +const acceptAmount = async (ctx, next) => { + const amount = Number(ctx.message.text); + if (amount > 0) { + next(); + } else { + ctx.reply('Invalid amount, try again:') + } +}; + +const showQR = async (ctx, link) => { + ctx.replyWithPhoto({url: 'https://chart.googleapis.com/chart?chs=360x360&cht=qr&chl=' + link + '&choe=UTF-8'}); +}; + +const showApproveLink = async (ctx) => { + const gasGram = 1; + const s = await createApprove(ctx.session.fromAmount); + const link = 'ton://transfer/' + ctx.session.fromAddr + '?amount=' + toGram(gasGram) + '&text=' + s; + + showQR(ctx, link); + + const message = ctx.session.fromAmount + ' ' + formatName(ctx.session.fromAddr) + ' -> ' + ctx.session.toAmount + ' ' + formatName(ctx.session.toAddr) + '\n\n' + + 'Please approve token transfer to dex:\n\n' + + link; + + ctx.reply(message, Markup + .keyboard([ + ['✅ Done!'], + ]) + .oneTime() + .resize() + .extra() + ); +}; + +const showOrderLink = async (ctx) => { + const s = await createOrder(ctx.session.fromAmount, ctx.session.fromAddr, ctx.session.toAmount, ctx.session.toAddr); + + const amount = ctx.session.fromAddr === '0' ? Number(ctx.session.fromAmount) + 2 : 2; + + const link = 'ton://transfer/' + dex_addr + '?amount=' + toGram(amount) + '&text=' + s; + + showQR(ctx, link); + + const message = ctx.session.fromAmount + ' ' + formatName(ctx.session.fromAddr) + ' -> ' + ctx.session.toAmount + ' ' + formatName(ctx.session.toAddr) + '\n\n' + + 'To create order please open this link with your TON wallet and send the transaction\n\n' + + link; + + ctx.reply(message, Markup + .keyboard([ + ['✅ Done!'], + ]) + .oneTime() + .resize() + .extra() + ); +}; + +const showState = (ctx) => { + switch (ctx.session.counter) { + case 0: + ctx.reply('Hello!', Markup + .keyboard([ + ['💰 New Order', '👛 My Orders'], + ]) + .oneTime() + .resize() + .extra() + ); + break; + + case 1: + ctx.reply('Please enter the currency you are giving:', Markup + .keyboard([ + ['💎 Gram'], + ['💰 TRC20 Token'], + ]) + .oneTime() + .resize() + .extra() + ); + break; + + case 2: + ctx.reply('Enter smart-contract address of TRC20 token:'); + break; + + case 3: + ctx.reply('Please enter the amount of ' + formatName(ctx.session.fromAddr) + ':'); + break; + + case 4: + ctx.reply('Please enter the currency you receive:', Markup + .keyboard([ + ['💎 Gram'], + ['💰 TRC20 Token'], + ]) + .oneTime() + .resize() + .extra() + ); + + break; + + case 5: + ctx.reply('Enter smart-contract address of TRC20 token:'); + break; + + case 6: + ctx.reply('Please enter the amount of ' + formatName(ctx.session.toAddr) + ':'); + break; + + case 7: + showApproveLink(ctx); + break; + + case 8: + showOrderLink(ctx); + break; + + case 10: + ctx.reply('Please enter your wallet address:'); + break; + } +}; + +const next = (ctx) => { + ctx.session.counter++; + showState(ctx); +}; + +exchangeBot.on('text', (ctx) => { + switch (ctx.session.counter) { + case 1: // enter from currency type + acceptCurrencyType(ctx, isToken => { + if (isToken) { + next(ctx); + } else { + ctx.session.fromAddr = '0'; + ctx.session.counter++; + next(ctx); + } + }); + break; + + case 2: // enter from address + ctx.session.fromAddr = ctx.message.text; + + acceptCurrency(ctx, () => next(ctx)); + break; + + case 3: // enter from amount + ctx.session.fromAmount = ctx.message.text; + + acceptAmount(ctx, () => { + if (ctx.session.fromAddr === '0') { + ctx.session.counter++; + } + next(ctx) + }); + break; + + case 4: // enter to currency type + acceptCurrencyType(ctx, isToken => { + if (isToken) { + next(ctx); + } else { + ctx.session.toAddr = '0'; + ctx.session.counter++; + next(ctx); + } + }); + break; + + case 5: // enter from address + ctx.session.toAddr = ctx.message.text; + + acceptCurrency(ctx, () => next(ctx)); + break; + + case 6: // enter to amount + ctx.session.toAmount = ctx.message.text; + + acceptAmount(ctx, () => { + ctx.session.counter++; + if (!currencies[ctx.session.fromAddr].isToken) { + ctx.session.counter++; + } + showState(ctx); + }); + break; + + case 7: // approve sended + next(ctx); + break; + + case 8: // create order sended + ctx.session.counter = 0; + showState(ctx); + break; + + case 10: // enter my address + ctx.session.myAddr = ctx.message.text; + + acceptAddress(ctx, () => { + ctx.session.counter++; + + getOrders(ctx) + .then(s => { + ctx.reply(s); + }) + .catch(err => { + console.log('system error'); + }); + }); + break; + } +}); + +exchangeBot.launch(); diff --git a/bot/approve.fif b/bot/approve.fif new file mode 100644 index 0000000..11a7ecf --- /dev/null +++ b/bot/approve.fif @@ -0,0 +1,14 @@ +#!/usr/bin/env fift -s +"TonUtil.fif" include +"Asm.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Creates an approve message body" cr 1 halt +} : usage +$# 2- -2 and ' usage if + +$1 $>smca drop drop 2dup 2constant spender_addr +$2 $>GR =: amount + + =: body_boc +body_boc smca .s \ No newline at end of file diff --git a/bot/config.sample.js b/bot/config.sample.js new file mode 100644 index 0000000..2cc8b7e --- /dev/null +++ b/bot/config.sample.js @@ -0,0 +1,4 @@ +module.exports = { + token: 'telegram_bot_secret', + path: '/Users/tolyayanot/' +} diff --git a/bot/insert-order.fif b/bot/insert-order.fif new file mode 100644 index 0000000..2d17363 --- /dev/null +++ b/bot/insert-order.fif @@ -0,0 +1,18 @@ +#!/usr/bin/env fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Creates a insert_order message body" cr + ."" cr 1 halt +} : usage +$# dup 6 < swap 6 > or ' usage if + +$1 $>GR =: from_value +$2 parse-int =: from_currency_type +from_currency_type { smca drop drop addr, b> } { } cond =: from_currency +$4 $>GR =: to_value +$5 parse-int =: to_currency_type +to_currency_type { smca drop drop addr, b> } { } cond =: to_currency + + +dup " cr + ."Creates a cancel_order message body" cr + ."" cr 1 halt +} : usage +$# dup 1 < swap 1 > or ' usage if + +$1 parse-int =: order_id + + +dup ."resulting external message: " B dup +"insert.boc" tuck B>file +."(Saved to file " type .")" cr diff --git a/dex.fc b/dex.fc new file mode 100644 index 0000000..ef1f45b --- /dev/null +++ b/dex.fc @@ -0,0 +1,394 @@ +slice clone_slice(slice s) { + return begin_cell().store_slice(s).end_cell().begin_parse(); +} + +slice pack_addr(int wc, slice addr) { + return begin_cell().store_int(wc, 8).store_slice(addr).end_cell().begin_parse(); +} + +(slice, slice) ~load_currency(slice cs) { + int currency_type = cs~load_uint(8); + if (currency_type) { + slice currency = cs~load_bits(264); + return (cs, currency); + } else { + slice currency = cs~load_bits(32); + return (cs, currency); + } +} + +builder store_currency(builder b, slice currency) { + int currency_type = currency.slice_bits() != 32; + return b.store_uint(currency_type ? 1 : 0, 8).store_slice(currency); +} + +(slice, int, slice, int, slice, int, int) parse_order(slice cs) { + slice ref = cs~load_ref().begin_parse(); + + return (cs~load_bits(264), cs~load_grams(), cs~load_currency(), ref~load_grams(), ref~load_currency(), ref~load_int(1), ref~load_uint(32)); +} + +builder store_order(sender, from_value, from_currency, to_value, to_currency, paid) { + int time = now(); +;; int time = 0; ;; todo: for test + + builder ref = begin_cell() + .store_grams(to_value) + .store_currency(to_currency) + .store_int(paid, 1) ;; paid + .store_uint(time + 86400, 32); ;; lifetime 1 day + + builder b = begin_cell() + .store_ref(ref.end_cell()) + .store_slice(sender) + .store_grams(from_value) + .store_currency(from_currency); + + return b; +} + +int is_gram(slice currency) { + return (currency.slice_bits() == 32) & (currency~load_int(32) == 0); +} + +int get_extra_currency(slice currency) { + if (currency.slice_bits() != 32) { + return 0; + } + return currency~load_int(32); +} + +() send_message_back(int action, slice sender, int value, int mode, int query_id, int query_action) impure { + ;; int_msg_info ihr_disabled:1 bounce:1 bounced:0 src:MsgAddress -> 011000 + var msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(0x4, 3) + .store_slice(sender) + .store_grams(value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(action, 32) + .store_uint(query_id, 64) + .store_uint(query_action, 32); + + send_raw_message(msg.end_cell(), mode); +} + +() transfer(slice addr, int value, slice currency, int order_id) impure { + int isgram = is_gram(currency); + int currency_id = get_extra_currency(currency); + + if (isgram) { + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(0x4, 3) + .store_slice(addr) + .store_grams(value) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1); + + send_raw_message(msg.end_cell(), 0); + + } elseif (currency_id) { + int gas_grams = 1 << 30; ;; ~1 gram of value to process + + cell currencies = new_dict(); + currencies~idict_set_builder(32, currency_id, begin_cell().store_grams(value)); + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(0x4, 3) + .store_slice(addr) + .store_grams(gas_grams) + .store_dict(currencies) + .store_uint(0, 4 + 4 + 64 + 32 + 1 + 1); + + send_raw_message(msg.end_cell(), 0); + + } else { + int gas_grams = 1 << 30; ;; ~1 gram of value to process and obtain answer + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(0x4, 3) + .store_slice(currency) + .store_grams(gas_grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(1, 32) ;; transfer + .store_uint(order_id, 64) + .store_slice(addr) + .store_grams(value); + + send_raw_message(msg.end_cell(), 0); + + } +} + +() transfer_from(slice addr, int value, slice currency, int order_id) impure { + int gas_grams = 1 << 30; ;; ~1 gram of value to process and obtain answer + + (int my_wc, slice my_val) = parse_var_addr(my_address()); + slice my_addr = pack_addr(my_wc, my_val); +;; slice my_addr = addr; ;; todo: for test + + builder ref = begin_cell() + .store_slice(addr) + .store_slice(my_addr) + .store_grams(value); + + builder msg = begin_cell() + .store_uint(0x18, 6) + .store_uint(0x4, 3) + .store_slice(currency) + .store_grams(gas_grams) + .store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1) + .store_uint(2, 32) ;; transfer from + .store_uint(order_id, 64) ;; transfer from + .store_ref(ref.end_cell()); + + send_raw_message(msg.end_cell(), 0); +} + +(cell, int, slice) find_match_order(cell orders, int _from_value, slice _from_currency, int _to_value, slice _to_currency) { + int result_id = 0; + slice result_sender = null(); + int time = now(); +;; int time = 0; ;; todo: for test + + int i = -1; + do { + (i, slice order_slice, int f) = orders.udict_get_next?(64, i); + if (f) { + (slice sender, int from_value, slice from_currency, int to_value, slice to_currency, int paid, int expired_at) = parse_order(order_slice); + + if (paid & (_from_value == to_value) & (_from_currency.slice_hash() == to_currency.slice_hash()) & + (_to_value == from_value) & (_to_currency.slice_hash() == from_currency.slice_hash())) { + result_id = i; + result_sender = sender; + } elseif (expired_at < time) { + if (paid) { + transfer(sender, from_value, from_currency, i); ;; return funds + } + + orders~udict_delete?(64, i); + } + } + } until ((~ f) | (result_id > 0)); + + return (orders, result_id, result_sender); +} + +(cell, int) try_match_order(cell orders, slice sender, int id, int from_value, slice from_currency, int to_value, slice to_currency) impure { + (orders, int match_order_id, slice match_order_sender) = find_match_order(orders, from_value, from_currency, to_value, to_currency); + + if (match_order_id > 0) { + transfer(sender, to_value, to_currency, id); + transfer(match_order_sender, from_value, from_currency, match_order_id); + + if (id > 0) { + orders~udict_delete?(64, id); + } + orders~udict_delete?(64, match_order_id); + } + + return (orders, match_order_id); +} + +() process_user_msg(int action, int msg_value, cell extra_currencies, slice sender, slice cs) impure { + int query_id = cs~load_uint(64); + + if (action > 2) { + if (action < 0x80000000) { ;; not response + send_message_back(0xffffffff, sender, 0, 64, query_id, action); + } + return (); + } + + slice ds = get_data().begin_parse(); + (int seqno, cell orders) = (ds~load_uint(64), ds~load_dict()); + ds.end_parse(); + int new_seqno = seqno; + int need_return_one_gram = 0; + + if (action == 1) { ;; insert order + int from_value = cs~load_grams(); + slice from_currency = cs~load_currency(); + int to_value = cs~load_grams(); + slice to_currency = cs~load_currency(); + + int matched = 0; + int isgram = is_gram(from_currency); + if (isgram) { + msg_value -= 1000000000; + need_return_one_gram = 1; + } + int currency_id = get_extra_currency(from_currency); + if (currency_id) { + (slice currency_slice, _) = extra_currencies.idict_get?(32, currency_id); + msg_value = currency_slice~load_grams(); + need_return_one_gram = 0; + isgram = -1; + } + + if (isgram) { + throw_unless(100, msg_value >= from_value); + (orders, matched) = try_match_order(orders, sender, 0, from_value, from_currency, to_value, to_currency); + } else { + transfer_from(sender, from_value, from_currency, seqno + 1); + } + + if (matched == 0) { + new_seqno = seqno + 1; + + builder b = store_order(sender, from_value, from_currency, to_value, to_currency, isgram); + orders~udict_set_builder(64, new_seqno, b); + } + + } elseif (action == 2) { ;; cancel order + + int order_id = cs~load_uint(64); + (slice order_slice, int order_found) = orders.udict_get?(64, order_id); + if (order_found) { + (slice order_sender, int from_value, slice from_currency, int to_value, slice to_currency, int paid, int expired_at) = parse_order(order_slice); + + if (order_sender.slice_hash() == sender.slice_hash()) { + if (paid) { + transfer(order_sender, from_value, from_currency, order_id); ;; return funds + } + + orders~udict_delete?(64, order_id); + } + } + + } + + set_data( + begin_cell() + .store_uint(new_seqno, 64) + .store_dict(orders) + .end_cell() + ); + + send_message_back(0x90000000, sender, need_return_one_gram ? 1000000000 : 0, need_return_one_gram ? 2 : 64, query_id, action); +} + +() process_trc20_reply(slice sender, slice cs, int ok) impure { + int query_id = cs~load_uint(64); + + if (ok & (cs~load_uint(32) != 2)) { ;; query_action != transfer_from + return (); + } + + slice ds = get_data().begin_parse(); + (int seqno, cell orders) = (ds~load_uint(64), ds~load_dict()); + ds.end_parse(); + + (slice order_slice, int order_found) = orders.udict_get?(64, query_id); + if (order_found) { + (slice order_sender, int from_value, slice from_currency, int to_value, slice to_currency, int paid, int expired_at) = parse_order(order_slice); + + if ((paid == 0) & (from_currency.slice_hash() == sender.slice_hash())) { + + if (ok) { + + (orders, int matched) = try_match_order(orders, order_sender, query_id, from_value, from_currency, to_value, to_currency); + if (matched == 0) { + builder b = store_order(order_sender, from_value, from_currency, to_value, to_currency, -1); + + orders~udict_set_builder(64, query_id, b); + } + } else { + orders~udict_delete?(64, query_id); + } + } + } + + set_data( + begin_cell() + .store_uint(seqno, 64) + .store_dict(orders) + .end_cell() + ); +} + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + slice cs = in_msg_cell.begin_parse(); + int flags = cs~load_uint(4); + slice sender_raw = cs~load_msg_addr(); + (int sender_wc, slice sender_addr) = parse_var_addr(sender_raw); + slice sender = pack_addr(sender_wc, sender_addr); + cs~load_grams(); ;; msg_value + cell extra_currencies = cs~load_dict(); + + cs = in_msg; + int action = cs~load_uint(32); + + if ((action == 0) & (cs.slice_bits() == 0)) { ;; just accept grams + return (); + } + + if (flags & 1) { ;; bounced + + if (action == 2) { ;; error reply from TRC20 transfer_from + process_trc20_reply(sender, cs, 0); + } + + } elseif (action == 0x80000000) { ;; ok reply from TRC20 transfer_from + + process_trc20_reply(sender, cs, 1); + + } else { + (action, cs) = parse_msg(action, cs); + process_user_msg(action, msg_value, extra_currencies, sender, cs); + } +} + +() recv_external(slice sender, slice in_msg) impure { + accept_message(); +} + +int seqno() method_id { + slice ds = get_data().begin_parse(); + (int seqno, cell orders) = (ds~load_uint(64), ds~load_dict()); + ds.end_parse(); + + return seqno; +} + +;; currency - 32bit for extra currency or 264 bit for TRC20 tokens +_ orders(slice _sender, slice _from_currency, slice _to_currency) method_id { + slice ds = get_data().begin_parse(); + (int seqno, cell orders) = (ds~load_uint(64), ds~load_dict()); + ds.end_parse(); + + var result = nil; + int i = -1; + do { + (i, slice order_slice, int f) = orders.udict_get_next?(64, i); + if (f) { + (slice sender, int from_value, slice from_currency, int to_value, slice to_currency, int paid, int expired_at) = parse_order(order_slice); + + int fit_sender = _sender.slice_empty?() | (_sender.slice_hash() == sender.slice_hash()); + int fit_from_currency = _from_currency.slice_empty?() | (_from_currency.slice_hash() == from_currency.slice_hash()); + int fit_to_currency = _to_currency.slice_empty?() | (_to_currency.slice_hash() == to_currency.slice_hash()); + + if (fit_sender & fit_from_currency & fit_to_currency) { + result = cons(triple(i, paid, triple(clone_slice(sender), pair(from_value, clone_slice(from_currency)), pair(to_value, clone_slice(to_currency)))), result); + } + } + } until (~ f); + + return result; +} + +_ my_orders(int sender_wc, int sender_addr) method_id { + slice sender = begin_cell().store_int(sender_wc, 8).store_uint(sender_addr, 256).end_cell().begin_parse(); + slice empty = begin_cell().end_cell().begin_parse(); + return orders(sender, empty, empty); +} + +;; TEST + +int test_recv_internal(int value, cell in_msg_cell, slice in_msg) method_id { + recv_internal(value, in_msg_cell, in_msg); + return 0; +} \ No newline at end of file diff --git a/dex.fif b/dex.fif new file mode 100644 index 0000000..c4ce85e --- /dev/null +++ b/dex.fif @@ -0,0 +1,1019 @@ +// automatically generated from `/Users/tolyayanot/lite-client/crypto/smartcont/stdlib.fc` `msg_hex_comment.fc` `dex/dex.fc` +PROGRAM{ + 85643 DECLMETHOD str2bin + DECLPROC parse_msg + DECLPROC clone_slice + DECLPROC pack_addr + DECLPROC ~load_currency + DECLPROC store_currency + DECLPROC parse_order + DECLPROC store_order + DECLPROC is_gram + DECLPROC get_extra_currency + DECLPROC send_message_back + DECLPROC transfer + DECLPROC transfer_from + DECLPROC find_match_order + DECLPROC try_match_order + DECLPROC process_user_msg + DECLPROC process_trc20_reply + DECLPROC recv_internal + DECLPROC recv_external + 85143 DECLMETHOD seqno + 65641 DECLMETHOD orders + 77572 DECLMETHOD my_orders + 90191 DECLMETHOD test_recv_internal + str2bin PROC:<{ + NEWC + OVER + SBITS + 3 RSHIFT# + 0 PUSHINT + WHILE:<{ + s0 s1 PUSH2 + LESS + }>DO<{ + s0 s3 XCHG + 8 LDU + OVER + 48 EQINT + IF:<{ + NIP + 0 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 49 EQINT + IF:<{ + NIP + 1 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 50 EQINT + IF:<{ + NIP + 2 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 51 EQINT + IF:<{ + NIP + 3 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 52 EQINT + IF:<{ + NIP + 4 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 53 EQINT + IF:<{ + NIP + 5 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 54 EQINT + IF:<{ + NIP + 6 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 55 EQINT + IF:<{ + NIP + 7 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 56 EQINT + IF:<{ + NIP + 8 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 57 EQINT + IF:<{ + NIP + 9 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 65 EQINT + IF:<{ + NIP + 10 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 66 EQINT + IF:<{ + NIP + 11 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 67 EQINT + IF:<{ + NIP + 12 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 68 EQINT + IF:<{ + NIP + 13 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + OVER + 69 EQINT + IF:<{ + NIP + 14 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + SWAP + 70 EQINT + IF:<{ + 15 PUSHINT + s0 s3 XCHG2 + 4 STU + }>ELSE<{ + s0 s2 XCHG + }> + }> + }> + }> + }> + }> + }> + }> + }> + }> + }> + }> + }> + }> + }> + }> + s0 s3 XCHG + INC + s2 s3 XCHG + }> + s2 s3 XCHG + 3 BLKDROP + ENDC + CTOS + }> + parse_msg PROC:<{ + DUP + SBITS + NEWC + s3 PUSH + 0 GTINT + IFJMP:<{ + -ROT + LDSLICEX + -ROT + STSLICER + OVER + SREFS + 0 GTINT + IF:<{ + SWAP + LDREF + DROP + CTOS + STSLICER + }>ELSE<{ + NIP + }> + ENDC + CTOS + }> + s3 POP + LDSLICEX + SWAP + str2bin CALLDICT + s1 s2 XCHG + STSLICER + OVER + SREFS + 0 GTINT + IF:<{ + SWAP + LDREF + DROP + CTOS + str2bin CALLDICT + STSLICER + }>ELSE<{ + NIP + }> + ENDC + CTOS + 32 LDU + }> + clone_slice PROC:<{ + NEWC + SWAP + STSLICER + ENDC + CTOS + }> + pack_addr PROC:<{ + NEWC + s1 s2 XCHG + 8 STI + SWAP + STSLICER + ENDC + CTOS + }> + ~load_currency PROC:<{ + 8 LDU + SWAP + IFJMP:<{ + 264 PUSHINT + LDSLICEX + SWAP + }> + 32 LDSLICE + SWAP + }> + store_currency PROC:<{ + DUP + SBITS + 32 NEQINT + IF:<{ + 1 PUSHINT + }>ELSE<{ + 0 PUSHINT + }> + ROT + 8 STU + SWAP + STSLICER + }> + parse_order PROC:<{ + LDREF + SWAP + CTOS + SWAP + 264 PUSHINT + LDSLICEX + LDGRAMS + ~load_currency CALLDICT + NIP + s0 s3 XCHG + LDGRAMS + ~load_currency CALLDICT + SWAP + 1 LDI + 32 LDU + DROP + s5 s6 XCHG + s4 s5 XCHG + }> + store_order PROC:<{ + NOW + 86400 PUSHINT + ADD + NEWC + s0 s4 XCHG2 + STGRAMS + ROT + store_currency CALLDICT + 1 STI + 32 STU + ENDC + NEWC + STREF + s0 s3 XCHG2 + STSLICER + SWAP + STGRAMS + SWAP + store_currency CALLDICT + }> + is_gram PROC:<{ + DUP + SBITS + 32 EQINT + SWAP + 32 LDI + DROP + 0 EQINT + AND + }> + get_extra_currency PROC:<{ + DUP + SBITS + 32 NEQINT + IFJMP:<{ + DROP + 0 PUSHINT + }> + 32 LDI + DROP + }> + send_message_back PROC:<{ + 0 PUSHINT + 4 PUSHINT + 24 PUSHINT + NEWC + 6 STU + 3 STU + s0 s6 XCHG2 + STSLICER + s0 s4 XCHG2 + STGRAMS + s1 s4 XCHG + 107 STU + s1 s4 XCHG + 32 STU + s1 s2 XCHG + 64 STU + 32 STU + ENDC + SWAP + SENDRAWMSG + }> + transfer PROC:<{ + OVER + is_gram CALLDICT + s2 PUSH + get_extra_currency CALLDICT + SWAP + IF:<{ + 3 BLKDROP + 0 PUSHINT + 4 PUSHINT + 24 PUSHINT + NEWC + 6 STU + 3 STU + s0 s3 XCHG2 + STSLICER + SWAP + STGRAMS + 107 STU + ENDC + 0 PUSHINT + SENDRAWMSG + }>ELSE<{ + DUP + IF:<{ + NIP + NIP + 30 PUSHPOW2 + NEWDICT + NEWC + s0 s4 XCHG2 + STGRAMS + s0 s0 s3 XCHG3 + 32 PUSHINT + DICTISETB + 0 PUSHINT + 4 PUSHINT + 24 PUSHINT + NEWC + 6 STU + 3 STU + s0 s4 XCHG2 + STSLICER + ROT + STGRAMS + STDICT + 106 STU + ENDC + 0 PUSHINT + SENDRAWMSG + }>ELSE<{ + DROP + 30 PUSHPOW2 + 1 PUSHINT + 0 PUSHINT + 4 PUSHINT + 24 PUSHINT + NEWC + 6 STU + 3 STU + s0 s5 XCHG2 + STSLICER + ROT + STGRAMS + s1 s3 XCHG + 107 STU + s1 s2 XCHG + 32 STU + 64 STU + ROT + STSLICER + SWAP + STGRAMS + ENDC + 0 PUSHINT + SENDRAWMSG + }> + }> + }> + transfer_from PROC:<{ + 30 PUSHPOW2 + MYADDR + REWRITEVARADDR + pack_addr CALLDICT + NEWC + s0 s6 XCHG2 + STSLICER + s0 s5 XCHG2 + STSLICER + s0 s3 XCHG2 + STGRAMS + ENDC + 2 PUSHINT + 0 PUSHINT + 4 PUSHINT + 24 PUSHINT + NEWC + 6 STU + 3 STU + s0 s4 XCHG2 + STSLICER + s0 s5 XCHG2 + STGRAMS + s1 s2 XCHG + 107 STU + s1 s3 XCHG + 32 STU + 64 STU + STREF + ENDC + 0 PUSHINT + SENDRAWMSG + }> + find_match_order PROC:<{ + 0 PUSHINT + PUSHNULL + NOW + -1 PUSHINT + UNTIL:<{ + s8 PUSH + 64 PUSHINT + DICTUGETNEXT + NULLSWAPIFNOT + NULLSWAPIFNOT + DUP + IF:<{ + s0 s2 XCHG + parse_order CALLDICT + s15 s3 PUXC + EQUAL + s1 s(-1) PUXC + AND + s14 PUSH + HASHSU + s0 s3 XCHG + HASHSU + s1 s3 XCHG + EQUAL + s1 s2 XCHG + AND + s12 s4 PUSH2 + EQUAL + AND + s11 PUSH + HASHSU + s4 PUSH + HASHSU + EQUAL + AND + IF:<{ + 4 BLKDROP + s4 POP + s4 POP + s3 PUSH + }>ELSE<{ + s1 s7 XCPU + LESS + IF:<{ + IF:<{ + s3 PUSH + transfer CALLDICT + }>ELSE<{ + 3 BLKDROP + }> + 64 PUSHINT + s1 s10 s10 PUXC2 + DICTUDEL + DROP + }>ELSE<{ + s4 s13 XCHG + 4 BLKDROP + }> + s0 s9 XCHG + s0 s4 XCHG + }> + }>ELSE<{ + s5 s2 XCHG2 + DROP + }> + SWAP + NOT + OVER + 0 GTINT + OR + s1 s4 XCHG + }> + 2DROP + s2 POP + s2 POP + s2 POP + s2 POP + }> + try_match_order PROC:<{ + s6 s3 s2 XCPU2 + s3 s8 PUSH2 + find_match_order CALLDICT + OVER + 0 GTINT + IF:<{ + s7 s3 s8 XCHG3 + s6 PUSH + transfer CALLDICT + s5 s5 s5 XCHG3 + s5 PUSH + transfer CALLDICT + DUP + 0 GTINT + IF:<{ + SWAP + 64 PUSHINT + DICTUDEL + DROP + }>ELSE<{ + DROP + }> + s1 s(-1) PUXC + 64 PUSHINT + DICTUDEL + DROP + }>ELSE<{ + s7 s8 s0 XCHG3 + 7 BLKDROP + }> + SWAP + }> + process_user_msg PROC:<{ + 64 LDU + s5 PUSH + 2 GTINT + IFJMP:<{ + DROP + s2 POP + s2 POP + s2 PUSH + 31 PUSHPOW2 + LESS + IF:<{ + 32 PUSHPOW2DEC + 0 PUSHINT + s4 s2 XCHG2 + 64 PUSHINT + s0 s2 XCHG + send_message_back CALLDICT + }>ELSE<{ + 3 BLKDROP + }> + }> + c4 PUSH + CTOS + 64 LDU + LDDICT + ENDS + OVER + 0 PUSHINT + s9 PUSH + 1 EQINT + IF:<{ + s0 s4 XCHG + LDGRAMS + ~load_currency CALLDICT + SWAP + LDGRAMS + ~load_currency CALLDICT + NIP + s7 s2 PUSH2 + is_gram CALLDICT + DUP + IF:<{ + s9 POP + s0 s12 XCHG + 1000000000 PUSHINT + SUB + 1 PUSHINT + }>ELSE<{ + s13 s9 XCHG2 + }> + s4 PUSH + get_extra_currency CALLDICT + DUP + IF:<{ + NIP + NIP + s8 POP + s7 s10 XCHG2 + 32 PUSHINT + DICTIGET + NULLSWAPIFNOT + DROP + LDGRAMS + DROP + s10 PUSH + -1 PUSHINT + }>ELSE<{ + s3 s13 XCHG + s4 s10 XCHG + s4 s3 s0 XCHG3 + 2DROP + }> + DUP + IF:<{ + s12 POP + s1 s3 XCPU + GEQ + 100 THROWIFNOT + s4 s8 XCPU + 0 PUSHINT + s4 s3 s8 PUSH3 + s14 PUSH + try_match_order CALLDICT + }>ELSE<{ + s2 POP + s6 PUSH + INC + s10 s4 s(-2) PU2XC + s5 s(-1) PUXC + transfer_from CALLDICT + s11 s5 XCHG2 + SWAP + }> + 0 EQINT + IF:<{ + s3 POP + s0 s4 XCHG + INC + s7 PUSH + s4 s1 s5 XCHG3 + s6 s9 s3 XCHG3 + s0 s10 XCHG + store_order CALLDICT + SWAP + 64 PUSHINT + s5 s6 s6 PUXC2 + DICTUSETB + }>ELSE<{ + s4 s10 XCHG + s3 s9 XCHG + s0 s6 XCHG + 6 BLKDROP + }> + }>ELSE<{ + s3 POP + s6 POP + s6 POP + s6 PUSH + 2 EQINT + IF:<{ + SWAP + 64 LDU + DROP + s0 s5 PUSH2 + 64 PUSHINT + DICTUGET + NULLSWAPIFNOT + IF:<{ + parse_order CALLDICT + s1 s3 XCHG + 3 BLKDROP + s3 PUSH + HASHSU + s8 PUSH + HASHSU + EQUAL + IF:<{ + IF:<{ + s3 PUSH + transfer CALLDICT + }>ELSE<{ + 3 BLKDROP + }> + s0 s5 XCHG2 + 64 PUSHINT + DICTUDEL + DROP + }>ELSE<{ + s5 s9 XCHG + 5 BLKDROP + }> + }>ELSE<{ + s2 s6 XCHG + 2DROP + }> + }>ELSE<{ + s5 s5 XCHG2 + DROP + }> + }> + NEWC + s1 s4 XCHG + 64 STU + s1 s3 XCHG + STDICT + ENDC + c4 POP + 2415919104 PUSHINT + s3 PUSH + IF:<{ + 1000000000 PUSHINT + }>ELSE<{ + 0 PUSHINT + }> + s0 s4 XCHG + IF:<{ + 2 PUSHINT + }>ELSE<{ + 64 PUSHINT + }> + 5 1 REVERSE + s0 s3 s3 XCHG3 + send_message_back CALLDICT + }> + process_trc20_reply PROC:<{ + SWAP + 64 LDU + 32 LDU + DROP + 2 NEQINT + s2 s(-1) PUXC + AND + IFJMP:<{ + 3 BLKDROP + }> + c4 PUSH + CTOS + 64 LDU + LDDICT + ENDS + s2 s0 PUSH2 + 64 PUSHINT + DICTUGET + NULLSWAPIFNOT + IF:<{ + parse_order CALLDICT + DROP + 0 EQINT + s3 PUSH + HASHSU + s0 s11 XCHG + HASHSU + s1 s11 XCHG + EQUAL + s1 s10 XCHG + AND + IF:<{ + s0 s7 XCHG + IF:<{ + s3 s2 s5 XCPU2 + s3 s5 s8 PUSH3 + s12 PUSH + try_match_order CALLDICT + 0 EQINT + IF:<{ + s4 s3 s0 XCHG3 + s1 s6 s7 XCHG3 + -1 PUSHINT + store_order CALLDICT + s0 s1 s3 XCHG3 + 64 PUSHINT + DICTUSETB + }>ELSE<{ + s4 s7 XCHG + s0 s6 XCHG + 6 BLKDROP + }> + }>ELSE<{ + 3 BLKDROP + s3 POP + s3 POP + SWAP + 64 PUSHINT + DICTUDEL + DROP + }> + }>ELSE<{ + s5 s8 XCHG + s4 s7 XCHG + 7 BLKDROP + }> + }>ELSE<{ + s5 s4 s0 XCHG3 + 4 BLKDROP + }> + NEWC + s1 s2 XCHG + 64 STU + STDICT + ENDC + c4 POP + }> + recv_internal PROC:<{ + SWAP + CTOS + 4 LDU + LDMSGADDR + SWAP + REWRITEVARADDR + pack_addr CALLDICT + SWAP + LDGRAMS + NIP + LDDICT + DROP + s0 s3 XCHG + 32 LDU + OVER + 0 EQINT + OVER + SBITS + 0 EQINT + AND + IFJMP:<{ + 6 BLKDROP + }> + s0 s3 XCHG + 1 PUSHINT + AND + IF:<{ + s3 POP + s3 POP + SWAP + 2 EQINT + IF:<{ + 0 PUSHINT + process_trc20_reply CALLDICT + }>ELSE<{ + 2DROP + }> + }>ELSE<{ + DUP + 31 PUSHPOW2 + EQUAL + IF:<{ + DROP + s2 POP + s2 POP + SWAP + 1 PUSHINT + process_trc20_reply CALLDICT + }>ELSE<{ + ROT + parse_msg CALLDICT + s1 s4 XCHG + s3 s3 s0 XCHG3 + process_user_msg CALLDICT + }> + }> + }> + recv_external PROC:<{ + 2DROP + ACCEPT + }> + seqno PROC:<{ + c4 PUSH + CTOS + 64 LDU + LDDICT + NIP + ENDS + }> + orders PROC:<{ + c4 PUSH + CTOS + 64 LDU + NIP + LDDICT + ENDS + PUSHNULL + -1 PUSHINT + UNTIL:<{ + s2 PUSH + 64 PUSHINT + DICTUGETNEXT + NULLSWAPIFNOT + NULLSWAPIFNOT + DUP + IF:<{ + s0 s2 XCHG + parse_order CALLDICT + DROP + s12 PUSH + SEMPTY + s13 PUSH + HASHSU + s7 PUSH + HASHSU + EQUAL + OR + s12 PUSH + SEMPTY + s13 PUSH + HASHSU + s6 PUSH + HASHSU + EQUAL + OR + s12 PUSH + SEMPTY + s13 PUSH + HASHSU + s5 PUSH + HASHSU + EQUAL + OR + -ROT + AND + SWAP + AND + IF:<{ + s0 s5 XCHG + clone_slice CALLDICT + s0 s3 XCHG + clone_slice CALLDICT + s1 s4 XCHG + PAIR + s0 s3 XCHG + clone_slice CALLDICT + PAIR + s1 s2 XCHG + TRIPLE + s2 s(-1) s(-1) PUXC2 + TRIPLE + s0 s3 XCHG2 + CONS + }>ELSE<{ + s6 s8 XCHG + 6 BLKDROP + }> + }>ELSE<{ + 2SWAP + DROP + }> + SWAP + NOT + s1 s2 XCHG + }> + s1 s5 XCHG + 5 BLKDROP + }> + my_orders PROC:<{ + NEWC + s1 s2 XCHG + 8 STI + 256 STU + ENDC + CTOS + NEWC + ENDC + CTOS + DUP + orders CALLDICT + }> + test_recv_internal PROC:<{ + recv_internal CALLDICT + 0 PUSHINT + }> +}END>c diff --git a/insert-order.fif b/insert-order.fif new file mode 100644 index 0000000..38f80d2 --- /dev/null +++ b/insert-order.fif @@ -0,0 +1,21 @@ +#!/usr/bin/env fift -s +"TonUtil.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Creates a insert_order message body" cr + ."" cr 1 halt +} : usage +$# dup 6 < swap 6 > or ' usage if + +$1 $>GR =: from_value +$2 parse-int =: from_currency_type +from_currency_type { } { } cond =: from_currency +$4 $>GR =: to_value +$5 parse-int =: to_currency_type +to_currency_type { } { } cond =: to_currency + + +dup ."resulting external message: " B dup +"insert.boc" tuck B>file +."(Saved to file " type .")" cr diff --git a/msg_hex_comment.fc b/msg_hex_comment.fc new file mode 100644 index 0000000..54daad9 --- /dev/null +++ b/msg_hex_comment.fc @@ -0,0 +1,86 @@ +;; Get binary message body from text message comment +;; text message comment must contain data in hexadecimal form (produced by fift's method 'csr.') + +slice str2bin(slice hex) method_id { + builder b = begin_cell(); + + int len = hex.slice_bits() / 8; + + int size = 4; + + int i = 0; + while (i < len) { + int char = hex~load_uint(8); + + if (char == 48) { ;; 0 + b~store_uint(0, size); + } elseif (char == 49) { ;; 1 + b~store_uint(1, size); + } elseif (char == 50) { ;; 2 + b~store_uint(2, size); + } elseif (char == 51) { ;; 3 + b~store_uint(3, size); + } elseif (char == 52) { ;; 4 + b~store_uint(4, size); + } elseif (char == 53) { ;; 5 + b~store_uint(5, size); + } elseif (char == 54) { ;; 6 + b~store_uint(6, size); + } elseif (char == 55) { ;; 7 + b~store_uint(7, size); + } elseif (char == 56) { ;; 8 + b~store_uint(8, size); + } elseif (char == 57) { ;; 9 + b~store_uint(9, size); + } elseif (char == 65) { ;; A + b~store_uint(10, size); + } elseif (char == 66) { ;; B + b~store_uint(11, size); + } elseif (char == 67) { ;; C + b~store_uint(12, size); + } elseif (char == 68) { ;; D + b~store_uint(13, size); + } elseif (char == 69) { ;; E + b~store_uint(14, size); + } elseif (char == 70) { ;; F + b~store_uint(15, size); + } + + i += 1; + } + + return b.end_cell().begin_parse(); +} + +(int, slice) parse_msg(int action, slice cs) { + + int len = cs.slice_bits(); + + builder tmp = begin_cell(); + + if (action > 0) { + tmp = tmp.store_slice(cs~load_bits(len)); + if (cs.slice_refs() > 0) { + cell ref0 = cs~load_ref(); + slice ref0_slice = ref0.begin_parse(); + tmp = tmp.store_slice(ref0_slice); + } + slice tmp_slice = tmp.end_cell().begin_parse(); + + return (action, tmp_slice); + } + + tmp = tmp.store_slice(str2bin(cs~load_bits(len))); + + if (cs.slice_refs() > 0) { + cell ref0 = cs~load_ref(); + slice ref0_slice = ref0.begin_parse(); + tmp = tmp.store_slice(str2bin(ref0_slice)); + } + + slice tmp_slice = tmp.end_cell().begin_parse(); + + action = tmp_slice~load_uint(32); + + return (action, tmp_slice); +} \ No newline at end of file diff --git a/new.fif b/new.fif new file mode 100644 index 0000000..afce57b --- /dev/null +++ b/new.fif @@ -0,0 +1,35 @@ +#!/usr/bin/env fift -s +"TonUtil.fif" include +"Asm.fif" include + +{ ."usage: " @' $0 type ." " cr + ."Creates a new exchange in specified workchain" cr 1 halt +} : usage +$# dup 1 < swap 1 > or ' usage if + +"dex" =: file_base +$1 parse-workchain-id =: wc + +"dex.fif" include =: code + + =: data + +null =: libs + + =: state_init + +state_init hashu wc swap 2dup 2constant contract_addr + +."dex address = " 2dup .addr cr +2dup file_base +".addr" save-address-verbose +."Non-bounceable address (for init): " 2dup 7 .Addr cr +."Bounceable address (for later access): " 6 .Addr cr + + =: init_msg + + =: external_msg +external_msg 2 boc+>B +file_base +"-create.boc" tuck B>file +."(Saved channel creating query to file " type .")" cr