chore: cleanup

This commit is contained in:
ppe
2023-01-19 11:13:45 +01:00
committed by just_ppe
parent e7336ced65
commit f1790fa546
19 changed files with 9 additions and 1230 deletions

View File

@@ -1,403 +0,0 @@
(() => {
// src/thetAR/actions/common.ts
var isAddress = (addr) => /[a-z0-9_-]{43}/i.test(addr);
var hashCheck = async (validHashs, contractTxId) => {
const tx = await SmartWeave.unsafeClient.transactions.get(contractTxId);
let SrcTxId;
tx.get("tags").forEach((tag) => {
let key = tag.get("name", { decode: true, string: true });
if (key === "Contract-Src") {
SrcTxId = tag.get("value", { decode: true, string: true });
}
});
if (!SrcTxId || !isAddress(SrcTxId)) {
throw new ContractError("Cannot find valid srcTxId in contract Tx content!");
}
const srcTx = await SmartWeave.unsafeClient.transactions.getData(SrcTxId, { decode: true, string: true });
if (srcTx.length < 1e4 && validHashs.includes(calcHash(srcTx))) {
return true;
}
return false;
};
var calcHash = (string) => {
var hash = 0, i, chr;
if (string.length === 0)
return hash;
for (i = 0; i < string.length; i++) {
chr = string.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0;
}
return hash;
};
var selectWeightedTokenHolder = async (balances) => {
let totalTokens = 0;
for (const address of Object.keys(balances)) {
totalTokens += balances[address];
}
let sum = 0;
const r = await getRandomIntNumber(totalTokens);
for (const address of Object.keys(balances)) {
sum += balances[address];
if (r <= sum && balances[address] > 0) {
return address;
}
}
return void 0;
};
async function getRandomIntNumber(max, uniqueValue = "") {
const pseudoRandomData = SmartWeave.arweave.utils.stringToBuffer(SmartWeave.block.height + SmartWeave.block.timestamp + SmartWeave.transaction.id + uniqueValue);
const hashBytes = await SmartWeave.arweave.crypto.hash(pseudoRandomData);
const randomBigInt = bigIntFromBytes(hashBytes);
return Number(randomBigInt % BigInt(max));
}
function bigIntFromBytes(byteArr) {
let hexString = "";
for (const byte of byteArr) {
hexString += byte.toString(16).padStart(2, "0");
}
return BigInt("0x" + hexString);
}
// src/thetAR/actions/write/addPair.ts
var addPair = async (state, action) => {
const param = action.input.params;
const tokenAddress = param.tokenAddress;
const logoTx = param.logo;
const description = param.description;
if (!isAddress(tokenAddress)) {
throw new ContractError("Token address format error!");
}
if (!isAddress(logoTx)) {
throw new ContractError("You should enter transaction id for Arweave of your logo!");
}
if (!validDescription(description)) {
throw new ContractError("Description you enter is not valid!");
}
if (action.caller !== state.owner) {
const txQty = SmartWeave.transaction.quantity;
const txTarget = SmartWeave.transaction.target;
if (txTarget !== state.owner) {
throw new ContractError("AddPair fee sent to wrong target!");
}
if (SmartWeave.arweave.ar.isLessThan(txQty, SmartWeave.arweave.ar.arToWinston("10"))) {
throw new ContractError("AddPair fee not right!");
}
if (!await hashCheck(state.tokenSrcTemplateHashs, tokenAddress)) {
throw new ContractError("Pst contract validation check failed!");
}
}
if (state.pairInfos.map((info) => info.tokenAddress).includes(tokenAddress)) {
throw new ContractError("Pair already exists!");
}
const tokenState = await SmartWeave.contracts.readContractState(tokenAddress);
state.maxPairId++;
state.pairInfos.push({
pairId: state.maxPairId,
tokenAddress,
logo: logoTx,
description,
name: tokenState.name,
symbol: tokenState.symbol,
decimals: tokenState.decimals
});
state.orderInfos[state.maxPairId] = {
currentPrice: void 0,
orders: []
};
for (const user in state.userOrders) {
if (Object.prototype.hasOwnProperty.call(state.userOrders, user)) {
let userOrder2 = state.userOrders[user];
userOrder2[state.maxPairId] = [];
}
}
return { state };
};
var validDescription = (desc) => /[a-z0-9_\s\:\/-]{1,128}/i.test(desc);
// src/thetAR/actions/write/createOrder.ts
var createOrder = async (state, action) => {
const param = action.input.params;
if (!(param.pairId <= state.maxPairId && param.pairId >= 0)) {
throw new ContractError("PairId not valid!");
}
if (param.price !== void 0 && param.price !== null) {
if (typeof param.price !== "number") {
throw new ContractError("Price must be a number!");
}
if (param.price <= 0 || !Number.isInteger(param.price)) {
throw new ContractError("Price must be positive integer!");
}
}
const newOrder = {
creator: action.caller,
orderId: SmartWeave.transaction.id,
direction: param.direction,
quantity: await checkOrderQuantity(state, action),
price: param.price
};
let selectedFeeRecvr = void 0;
try {
selectedFeeRecvr = await selectWeightedTokenHolder(await tokenBalances(state.thetarTokenAddress));
} catch {
}
const { newOrderbook, newUserOrders, transactions, currentPrice } = await matchOrder(newOrder, state.orderInfos[param.pairId].orders, state.userOrders, param.pairId, action.caller, state.feeRatio, selectedFeeRecvr);
state.orderInfos[param.pairId].orders = newOrderbook;
state.userOrders = newUserOrders;
if (!isNaN(currentPrice) && isFinite(currentPrice)) {
state.orderInfos[param.pairId].currentPrice = currentPrice;
}
for await (const tx of transactions) {
const matchedPair = state.pairInfos.find((i) => i.pairId === param.pairId);
const targetTokenAdrress = tx.tokenType === "dominent" ? state.thetarTokenAddress : matchedPair.tokenAddress;
await SmartWeave.contracts.write(targetTokenAdrress, { function: "transfer", to: tx.to, amount: tx.quantity });
}
return { state };
};
var tokenBalances = async (tokenAddress) => {
return (await SmartWeave.contracts.readContractState(tokenAddress)).balances;
};
var checkOrderQuantity = async (state, action) => {
const param = action.input.params;
let pairInfo2 = state.pairInfos.find((pair) => pair.pairId === param.pairId);
const tokenAddress = param.direction === "buy" ? state.thetarTokenAddress : pairInfo2.tokenAddress;
const tokenState = await SmartWeave.contracts.readContractState(tokenAddress);
let orderQuantity = tokenState.allowances[action.caller][SmartWeave.contract.id];
await SmartWeave.contracts.write(tokenAddress, { function: "transferFrom", from: action.caller, to: SmartWeave.contract.id, amount: orderQuantity });
if (param.direction === "buy" && param.price) {
orderQuantity = Math.floor(orderQuantity / param.price);
}
return orderQuantity;
};
var matchOrder = async (newOrder, orderbook, userOrders, newOrderPairId, caller, feeRatio, selectedFeeRecvr) => {
let transactions = Array();
const targetSortDirection = newOrder.direction === "buy" ? "sell" : "buy";
let totalTradePrice = 0;
let totalTradeVolume = 0;
const reverseOrderbook = orderbook.filter((order) => order.direction === targetSortDirection).sort((a, b) => {
if (newOrder.direction === "buy") {
return a.price > b.price ? 1 : -1;
} else {
return a.price > b.price ? -1 : 1;
}
});
const orderType = newOrder.price ? "limit" : "market";
if (reverseOrderbook.length === 0 && orderType === "market") {
throw new ContractError(`The first order must be limit type!`);
}
const newOrderTokenType = orderType === "market" && newOrder.direction === "buy" ? "dominent" : "trade";
for (let i = 0; i < reverseOrderbook.length; i++) {
const order = reverseOrderbook[i];
if (orderType === "limit" && order.price !== newOrder.price) {
continue;
}
const targetPrice = order.price;
const orderAmount = order.quantity;
const newOrderAmoumt = newOrderTokenType === "trade" ? newOrder.quantity : Math.floor(newOrder.quantity / targetPrice);
const targetAmout = orderAmount < newOrderAmoumt ? orderAmount : newOrderAmoumt;
totalTradePrice += targetPrice * targetAmout;
totalTradeVolume += targetAmout;
if (targetAmout === 0) {
break;
}
const dominentFee = Math.floor(targetAmout * targetPrice * feeRatio);
const tradeFee = Math.floor(targetAmout * feeRatio);
const dominentSwap = targetAmout * targetPrice - dominentFee;
const tradeSwap = targetAmout - tradeFee;
const buyer = newOrder.direction === "buy" ? newOrder : order;
const seller = newOrder.direction === "buy" ? order : newOrder;
transactions.push({
tokenType: "dominent",
to: seller.creator,
quantity: dominentSwap
});
transactions.push({
tokenType: "trade",
to: buyer.creator,
quantity: tradeSwap
});
if (selectedFeeRecvr) {
transactions.push({
tokenType: "dominent",
to: selectedFeeRecvr,
quantity: dominentFee
});
transactions.push({
tokenType: "trade",
to: selectedFeeRecvr,
quantity: tradeFee
});
}
order.quantity -= targetAmout;
if (order.quantity === 0) {
orderbook = orderbook.filter((v) => v.orderId !== order.orderId);
}
let userOrderInfos = userOrders[order.creator][newOrderPairId];
let matchedOrderIdx = userOrderInfos.findIndex((value) => value.orderId === order.orderId);
userOrderInfos[matchedOrderIdx].quantity -= targetAmout;
if (userOrderInfos[matchedOrderIdx].quantity === 0) {
userOrders[order.creator][newOrderPairId] = userOrderInfos.filter((v) => v.orderId !== order.orderId);
}
newOrder.quantity -= newOrderTokenType === "trade" ? targetAmout : targetAmout * targetPrice;
}
if (orderType === "market" && newOrder.quantity !== 0) {
transactions.push({
tokenType: newOrderTokenType,
to: newOrder.creator,
quantity: newOrder.quantity
});
newOrder.quantity = 0;
}
if (orderType === "limit" && newOrder.quantity !== 0) {
orderbook.push({ ...newOrder });
}
if (newOrder.quantity !== 0) {
if (userOrders[caller] === void 0) {
userOrders[caller] = {};
}
if (userOrders[caller][newOrderPairId] === void 0) {
userOrders[caller][newOrderPairId] = [];
}
userOrders[caller][newOrderPairId].push({ ...newOrder });
}
return {
newOrderbook: orderbook,
newUserOrders: userOrders,
transactions,
currentPrice: totalTradePrice / totalTradeVolume
};
};
// src/thetAR/actions/write/deposit.ts
var deposit = async (state, action) => {
logger.error("Token: " + action.input.params.token);
logger.error("Amount: " + action.input.params.amount);
await SmartWeave.contracts.write(action.input.params.token, {
function: "transferFrom",
from: action.caller,
to: SmartWeave.contract.id,
amount: action.input.params.amount
});
return { state };
};
// src/thetAR/actions/write/cancelOrder.ts
var cancelOrder = async (state, action) => {
const param = action.input.params;
const orderId = param.orderId;
const pairId = param.pairId;
if (!isAddress(orderId)) {
throw new ContractError(`OrderId not found: ${param.orderId}!`);
}
if (!(param.pairId <= state.maxPairId && param.pairId >= 0)) {
throw new ContractError("PairId not valid!");
}
const orderInfo2 = state.userOrders[action.caller][pairId].find((v) => v.orderId === orderId);
const pairInfo2 = state.pairInfos.find((i) => i.pairId === pairId);
if (!orderInfo2) {
throw new ContractError(`Cannot get access to pairId: ${pairId}!`);
}
if (!pairInfo2) {
throw new ContractError(`Pair info record not found: ${pairId}!`);
}
const tokenAddress = orderInfo2.direction === "buy" ? state.thetarTokenAddress : pairInfo2.tokenAddress;
const quantity = orderInfo2.direction === "buy" ? orderInfo2.price * orderInfo2.quantity : orderInfo2.quantity;
await SmartWeave.contracts.write(tokenAddress, { function: "transfer", to: action.caller, amount: quantity });
let ordersForUser = state.userOrders[action.caller][pairId];
state.userOrders[action.caller][pairId] = ordersForUser.filter((i) => i.orderId !== orderId);
let ordersForPair = state.orderInfos[pairId].orders;
state.orderInfos[pairId].orders = ordersForPair.filter((i) => i.orderId !== orderId);
return { state };
};
// src/thetAR/actions/write/addTokenHash.ts
var addTokenHash = async (state, action) => {
const param = action.input.params;
const hash = param.hash;
if (action.caller !== state.owner) {
throw new ContractError("You have no permission to modify hash list!");
}
state.tokenSrcTemplateHashs.push(hash);
return { state };
};
// src/thetAR/actions/read/pairInfo.ts
var pairInfo = async (state, action) => {
const param = action.input.params;
let pairId = param.pairId;
let result;
if (!Number.isInteger(pairId) || pairId < 0 || pairId > state.maxPairId) {
throw new ContractError(`Invalid pairId!`);
}
result = state.pairInfos.filter((i) => i.pairId === pairId)[0];
return { result };
};
// src/thetAR/actions/read/pairInfos.ts
var pairInfos = async (state, action) => {
let result;
result = state.pairInfos;
return { result };
};
// src/thetAR/actions/read/orderInfos.ts
var orderInfos = async (state, action) => {
let result;
result = state.orderInfos;
return { result };
};
// src/thetAR/actions/read/orderInfo.ts
var orderInfo = async (state, action) => {
const param = action.input.params;
let pairId = param.pairId;
let result;
if (!Number.isInteger(pairId) || pairId < 0 || pairId > state.maxPairId) {
throw new ContractError(`Invalid pairId!`);
}
result = state.orderInfos[pairId];
return { result };
};
// src/thetAR/actions/read/userOrder.ts
var userOrder = async (state, action) => {
const param = action.input.params;
let address = param.address;
let result;
if (!isAddress(address)) {
throw new ContractError(`Invalid wallet address!`);
}
result = state.userOrders[address];
return { result };
};
// src/thetAR/contract.ts
async function handle(state, action) {
const func = action.input.function;
switch (func) {
case "addPair":
return await addPair(state, action);
case "createOrder":
return await createOrder(state, action);
case "cancelOrder":
return await cancelOrder(state, action);
case "pairInfo":
return await pairInfo(state, action);
case "pairInfos":
return await pairInfos(state, action);
case "orderInfo":
return await orderInfo(state, action);
case "orderInfos":
return await orderInfos(state, action);
case "addTokenHash":
return await addTokenHash(state, action);
case "userOrder":
return await userOrder(state, action);
case "deposit":
return await deposit(state, action);
default:
throw new ContractError(`No function supplied or function not recognised: "${func}"`);
}
}
})();

View File

@@ -1,411 +0,0 @@
(() => {
// src/thetAR/actions/common.ts
var isAddress = (addr) => /[a-z0-9_-]{43}/i.test(addr);
var hashCheck = async (validHashs, contractTxId) => {
const tx = await SmartWeave.unsafeClient.transactions.get(contractTxId);
let SrcTxId;
tx.get("tags").forEach((tag) => {
let key = tag.get("name", { decode: true, string: true });
if (key === "Contract-Src") {
SrcTxId = tag.get("value", { decode: true, string: true });
}
});
if (!SrcTxId || !isAddress(SrcTxId)) {
throw new ContractError("Cannot find valid srcTxId in contract Tx content!");
}
const srcTx = await SmartWeave.unsafeClient.transactions.getData(SrcTxId, { decode: true, string: true });
if (srcTx.length < 1e4 && validHashs.includes(calcHash(srcTx))) {
return true;
}
return false;
};
var calcHash = (string) => {
var hash = 0, i, chr;
if (string.length === 0)
return hash;
for (i = 0; i < string.length; i++) {
chr = string.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0;
}
return hash;
};
var selectWeightedTokenHolder = async (balances) => {
let totalTokens = 0;
for (const address of Object.keys(balances)) {
totalTokens += balances[address];
}
let sum = 0;
const r = await getRandomIntNumber(totalTokens);
for (const address of Object.keys(balances)) {
sum += balances[address];
if (r <= sum && balances[address] > 0) {
return address;
}
}
return void 0;
};
async function getRandomIntNumber(max, uniqueValue = "") {
const pseudoRandomData = SmartWeave.arweave.utils.stringToBuffer(SmartWeave.block.height + SmartWeave.block.timestamp + SmartWeave.transaction.id + uniqueValue);
const hashBytes = await SmartWeave.arweave.crypto.hash(pseudoRandomData);
const randomBigInt = bigIntFromBytes(hashBytes);
return Number(randomBigInt % BigInt(max));
}
function bigIntFromBytes(byteArr) {
let hexString = "";
for (const byte of byteArr) {
hexString += byte.toString(16).padStart(2, "0");
}
return BigInt("0x" + hexString);
}
// src/thetAR/actions/write/addPair.ts
var addPair = async (state, action) => {
const param = action.input.params;
const tokenAddress = param.tokenAddress;
const logoTx = param.logo;
const description = param.description;
if (!isAddress(tokenAddress)) {
throw new ContractError("Token address format error!");
}
if (!isAddress(logoTx)) {
throw new ContractError("You should enter transaction id for Arweave of your logo!");
}
if (!validDescription(description)) {
throw new ContractError("Description you enter is not valid!");
}
if (action.caller !== state.owner) {
const txQty = SmartWeave.transaction.quantity;
const txTarget = SmartWeave.transaction.target;
if (txTarget !== state.owner) {
throw new ContractError("AddPair fee sent to wrong target!");
}
if (SmartWeave.arweave.ar.isLessThan(txQty, SmartWeave.arweave.ar.arToWinston("10"))) {
throw new ContractError("AddPair fee not right!");
}
if (!await hashCheck(state.tokenSrcTemplateHashs, tokenAddress)) {
throw new ContractError("Pst contract validation check failed!");
}
}
if (state.pairInfos.map((info) => info.tokenAddress).includes(tokenAddress)) {
throw new ContractError("Pair already exists!");
}
const tokenState = await SmartWeave.contracts.readContractState(tokenAddress);
state.maxPairId++;
state.pairInfos.push({
pairId: state.maxPairId,
tokenAddress,
logo: logoTx,
description,
name: tokenState.name,
symbol: tokenState.symbol,
decimals: tokenState.decimals
});
state.orderInfos[state.maxPairId] = {
currentPrice: void 0,
orders: []
};
for (const user in state.userOrders) {
if (Object.prototype.hasOwnProperty.call(state.userOrders, user)) {
let userOrder2 = state.userOrders[user];
userOrder2[state.maxPairId] = [];
}
}
return { state };
};
var validDescription = (desc) => /[a-z0-9_\s\:\/-]{1,128}/i.test(desc);
// src/thetAR/actions/write/createOrder.ts
var createOrder = async (state, action) => {
const param = action.input.params;
if (!(param.pairId <= state.maxPairId && param.pairId >= 0)) {
throw new ContractError("PairId not valid!");
}
if (param.price !== void 0 && param.price !== null) {
if (typeof param.price !== "number") {
throw new ContractError("Price must be a number!");
}
if (param.price <= 0 || !Number.isInteger(param.price)) {
throw new ContractError("Price must be positive integer!");
}
}
let {orderQuantity, updatedState} = await checkOrderQuantity(state, action);
const newOrder = {
creator: action.caller,
orderId: SmartWeave.transaction.id,
direction: param.direction,
quantity: orderQuantity,
price: param.price
};
let selectedFeeRecvr = void 0;
try {
selectedFeeRecvr = await selectWeightedTokenHolder(tokenBalances(updatedState));
} catch {
}
const { newOrderbook, newUserOrders, transactions, currentPrice } = await matchOrder(newOrder, state.orderInfos[param.pairId].orders, state.userOrders, param.pairId, action.caller, state.feeRatio, selectedFeeRecvr);
state.orderInfos[param.pairId].orders = newOrderbook;
state.userOrders = newUserOrders;
if (!isNaN(currentPrice) && isFinite(currentPrice)) {
state.orderInfos[param.pairId].currentPrice = currentPrice;
}
for await (const tx of transactions) {
const matchedPair = state.pairInfos.find((i) => i.pairId === param.pairId);
const targetTokenAdrress = tx.tokenType === "dominent" ? state.thetarTokenAddress : matchedPair.tokenAddress;
await SmartWeave.contracts.write(targetTokenAdrress, { function: "transfer", to: tx.to, amount: tx.quantity });
}
return { state };
};
var tokenBalances = (updatedState) => {
return updatedState.balances;
};
var checkOrderQuantity = async (state, action) => {
const param = action.input.params;
let pairInfo2 = state.pairInfos.find((pair) => pair.pairId === param.pairId);
const tokenAddress = param.direction === "buy" ? state.thetarTokenAddress : pairInfo2.tokenAddress;
const tokenState = await SmartWeave.contracts.readContractState(tokenAddress);
//let orderQuantity = param.price;
let orderQuantity = tokenState.allowances[action.caller][SmartWeave.contract.id];
//WASM version
//await SmartWeave.contracts.write(tokenAddress, { function: "transferFrom", from: action.caller, to: SmartWeave.contract.id, amount: orderQuantity });
//JS version
logger.error("CREATE Taking tokens: " + orderQuantity);
updatedState = (await SmartWeave.contracts.write(tokenAddress, { function: "transferFrom", sender: action.caller, recipient: SmartWeave.contract.id, amount: orderQuantity })).state;
logger.error("STATE:", updatedState);
if (param.direction === "buy" && param.price) {
orderQuantity = Math.floor(orderQuantity / param.price);
}
return {orderQuantity, updatedState};
};
var matchOrder = async (newOrder, orderbook, userOrders, newOrderPairId, caller, feeRatio, selectedFeeRecvr) => {
let transactions = Array();
const targetSortDirection = newOrder.direction === "buy" ? "sell" : "buy";
let totalTradePrice = 0;
let totalTradeVolume = 0;
const reverseOrderbook = orderbook.filter((order) => order.direction === targetSortDirection).sort((a, b) => {
if (newOrder.direction === "buy") {
return a.price > b.price ? 1 : -1;
} else {
return a.price > b.price ? -1 : 1;
}
});
const orderType = newOrder.price ? "limit" : "market";
if (reverseOrderbook.length === 0 && orderType === "market") {
throw new ContractError(`The first order must be limit type!`);
}
const newOrderTokenType = orderType === "market" && newOrder.direction === "buy" ? "dominent" : "trade";
for (let i = 0; i < reverseOrderbook.length; i++) {
const order = reverseOrderbook[i];
if (orderType === "limit" && order.price !== newOrder.price) {
continue;
}
const targetPrice = order.price;
const orderAmount = order.quantity;
const newOrderAmoumt = newOrderTokenType === "trade" ? newOrder.quantity : Math.floor(newOrder.quantity / targetPrice);
const targetAmout = orderAmount < newOrderAmoumt ? orderAmount : newOrderAmoumt;
totalTradePrice += targetPrice * targetAmout;
totalTradeVolume += targetAmout;
if (targetAmout === 0) {
break;
}
const dominentFee = Math.floor(targetAmout * targetPrice * feeRatio);
const tradeFee = Math.floor(targetAmout * feeRatio);
const dominentSwap = targetAmout * targetPrice - dominentFee;
const tradeSwap = targetAmout - tradeFee;
const buyer = newOrder.direction === "buy" ? newOrder : order;
const seller = newOrder.direction === "buy" ? order : newOrder;
transactions.push({
tokenType: "dominent",
to: seller.creator,
quantity: dominentSwap
});
transactions.push({
tokenType: "trade",
to: buyer.creator,
quantity: tradeSwap
});
if (selectedFeeRecvr) {
transactions.push({
tokenType: "dominent",
to: selectedFeeRecvr,
quantity: dominentFee
});
transactions.push({
tokenType: "trade",
to: selectedFeeRecvr,
quantity: tradeFee
});
}
order.quantity -= targetAmout;
if (order.quantity === 0) {
orderbook = orderbook.filter((v) => v.orderId !== order.orderId);
}
let userOrderInfos = userOrders[order.creator][newOrderPairId];
let matchedOrderIdx = userOrderInfos.findIndex((value) => value.orderId === order.orderId);
userOrderInfos[matchedOrderIdx].quantity -= targetAmout;
if (userOrderInfos[matchedOrderIdx].quantity === 0) {
userOrders[order.creator][newOrderPairId] = userOrderInfos.filter((v) => v.orderId !== order.orderId);
}
newOrder.quantity -= newOrderTokenType === "trade" ? targetAmout : targetAmout * targetPrice;
}
if (orderType === "market" && newOrder.quantity !== 0) {
transactions.push({
tokenType: newOrderTokenType,
to: newOrder.creator,
quantity: newOrder.quantity
});
newOrder.quantity = 0;
}
if (orderType === "limit" && newOrder.quantity !== 0) {
orderbook.push({ ...newOrder });
}
if (newOrder.quantity !== 0) {
if (userOrders[caller] === void 0) {
userOrders[caller] = {};
}
if (userOrders[caller][newOrderPairId] === void 0) {
userOrders[caller][newOrderPairId] = [];
}
userOrders[caller][newOrderPairId].push({ ...newOrder });
}
return {
newOrderbook: orderbook,
newUserOrders: userOrders,
transactions,
currentPrice: totalTradePrice / totalTradeVolume
};
};
// src/thetAR/actions/write/deposit.ts
var deposit = async (state, action) => {
logger.error("Token: " + action.input.params.token);
logger.error("Amount: " + action.input.params.amount);
await SmartWeave.contracts.write(action.input.params.token, {
function: "transferFrom",
from: action.caller,
to: SmartWeave.contract.id,
amount: action.input.params.amount
});
return { state };
};
// src/thetAR/actions/write/cancelOrder.ts
var cancelOrder = async (state, action) => {
const param = action.input.params;
const orderId = param.orderId;
const pairId = param.pairId;
if (!isAddress(orderId)) {
throw new ContractError(`OrderId not found: ${param.orderId}!`);
}
if (!(param.pairId <= state.maxPairId && param.pairId >= 0)) {
throw new ContractError("PairId not valid!");
}
const orderInfo2 = state.userOrders[action.caller][pairId].find((v) => v.orderId === orderId);
const pairInfo2 = state.pairInfos.find((i) => i.pairId === pairId);
if (!orderInfo2) {
throw new ContractError(`Cannot get access to pairId: ${pairId}!`);
}
if (!pairInfo2) {
throw new ContractError(`Pair info record not found: ${pairId}!`);
}
const tokenAddress = orderInfo2.direction === "buy" ? state.thetarTokenAddress : pairInfo2.tokenAddress;
const quantity = orderInfo2.direction === "buy" ? orderInfo2.price * orderInfo2.quantity : orderInfo2.quantity;
logger.error("CANCEL Returning tokens: " + quantity);
await SmartWeave.contracts.write(tokenAddress, { function: "transfer", to: action.caller, amount: quantity });
let ordersForUser = state.userOrders[action.caller][pairId];
state.userOrders[action.caller][pairId] = ordersForUser.filter((i) => i.orderId !== orderId);
let ordersForPair = state.orderInfos[pairId].orders;
state.orderInfos[pairId].orders = ordersForPair.filter((i) => i.orderId !== orderId);
return { state };
};
// src/thetAR/actions/write/addTokenHash.ts
var addTokenHash = async (state, action) => {
const param = action.input.params;
const hash = param.hash;
if (action.caller !== state.owner) {
throw new ContractError("You have no permission to modify hash list!");
}
state.tokenSrcTemplateHashs.push(hash);
return { state };
};
// src/thetAR/actions/read/pairInfo.ts
var pairInfo = async (state, action) => {
const param = action.input.params;
let pairId = param.pairId;
let result;
if (!Number.isInteger(pairId) || pairId < 0 || pairId > state.maxPairId) {
throw new ContractError(`Invalid pairId!`);
}
result = state.pairInfos.filter((i) => i.pairId === pairId)[0];
return { result };
};
// src/thetAR/actions/read/pairInfos.ts
var pairInfos = async (state, action) => {
let result;
result = state.pairInfos;
return { result };
};
// src/thetAR/actions/read/orderInfos.ts
var orderInfos = async (state, action) => {
let result;
result = state.orderInfos;
return { result };
};
// src/thetAR/actions/read/orderInfo.ts
var orderInfo = async (state, action) => {
const param = action.input.params;
let pairId = param.pairId;
let result;
if (!Number.isInteger(pairId) || pairId < 0 || pairId > state.maxPairId) {
throw new ContractError(`Invalid pairId!`);
}
result = state.orderInfos[pairId];
return { result };
};
// src/thetAR/actions/read/userOrder.ts
var userOrder = async (state, action) => {
const param = action.input.params;
let address = param.address;
let result;
if (!isAddress(address)) {
throw new ContractError(`Invalid wallet address!`);
}
result = state.userOrders[address];
return { result };
};
// src/thetAR/contract.ts
async function handle(state, action) {
const func = action.input.function;
switch (func) {
case "addPair":
return await addPair(state, action);
case "createOrder":
return await createOrder(state, action);
case "cancelOrder":
return await cancelOrder(state, action);
case "pairInfo":
return await pairInfo(state, action);
case "pairInfos":
return await pairInfos(state, action);
case "orderInfo":
return await orderInfo(state, action);
case "orderInfos":
return await orderInfos(state, action);
case "addTokenHash":
return await addTokenHash(state, action);
case "userOrder":
return await userOrder(state, action);
case "deposit":
return await deposit(state, action);
default:
throw new ContractError(`No function supplied or function not recognised: "${func}"`);
}
}
})();

View File

@@ -1,56 +0,0 @@
use serde::{Deserialize, Serialize};
use warp_wasm_utils::contract_utils::handler_result::HandlerResult;
use crate::error::ContractError;
use crate::state::State;
#[derive(Deserialize)]
#[serde(rename_all = "camelCase", tag = "function")]
pub enum Action {
Transfer {
to: String,
amount: u64,
},
TransferFrom {
from: String,
to: String,
amount: u64
},
BalanceOf {
target: String
},
TotalSupply {
},
Approve {
spender: String,
amount: u64,
},
Allowance {
owner: String,
spender: String
},
Evolve {
value: String
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub enum QueryResponseMsg {
Balance {
ticker: String,
target: String,
balance: u64,
},
Allowance {
ticker: String,
owner: String,
spender: String,
allowance: u64,
},
TotalSupply {
value: u64
}
}
pub type ActionResult = Result<HandlerResult<State, QueryResponseMsg>, ContractError>;

View File

@@ -1,57 +0,0 @@
use crate::state::State;
use std::collections::HashMap;
use warp_wasm_utils::contract_utils::handler_result::HandlerResult;
use crate::action::{QueryResponseMsg::Allowance, ActionResult};
use warp_wasm_utils::contract_utils::handler_result::HandlerResult::QueryResponse;
use warp_wasm_utils::contract_utils::js_imports::{Transaction};
pub fn allowance(state: State, owner: String, spender: String) -> ActionResult {
Ok(QueryResponse(
Allowance {
ticker: state.symbol,
allowance: __get_allowance(&state.allowances, &owner, &spender),
owner,
spender
}
))
}
pub fn approve(mut state: State, spender: String, amount: u64) -> ActionResult {
let caller = Transaction::owner();
__set_allowance(&mut state.allowances, caller, spender, amount);
Ok(HandlerResult::NewState(state))
}
//Following: https://users.rust-lang.org/t/use-of-pub-for-non-public-apis/40480
// Not a part of the contract API - used internally within the crate.
#[doc(hidden)]
pub fn __set_allowance(allowances: &mut HashMap<String, HashMap<String, u64>>, owner: String, spender: String, amount: u64) {
if amount > 0 {
*allowances
.entry(owner)
.or_default()
.entry(spender)
.or_default() = amount;
} else { //Prune state
match allowances.get_mut(&owner) {
Some(spender_allowances) => {
spender_allowances.remove(&spender);
if spender_allowances.is_empty() {
allowances.remove(&owner);
}
}
None => ()
}
}
}
//Following: https://users.rust-lang.org/t/use-of-pub-for-non-public-apis/40480
// Not a part of the contract API - used internally within the crate.
#[doc(hidden)]
pub fn __get_allowance(allowances: &HashMap<String, HashMap<String, u64>>, owner: &String, spender: &String) -> u64 {
return *allowances
.get(owner)
.map_or(&0, |spenders| {
spenders.get(spender).unwrap_or(&0)
});
}

View File

@@ -1,23 +0,0 @@
use crate::state::State;
use crate::action::{QueryResponseMsg::Balance,QueryResponseMsg::TotalSupply, ActionResult};
use warp_wasm_utils::contract_utils::handler_result::HandlerResult::QueryResponse;
pub fn balance_of(state: State, target: String) -> ActionResult {
Ok(QueryResponse(
Balance {
balance: *state.balances.get( & target).unwrap_or(&0),
ticker: state.symbol,
target
}
))
}
pub fn total_supply(state: State) -> ActionResult {
Ok(QueryResponse(
TotalSupply {
value: state.total_supply
}
))
}

View File

@@ -1,17 +0,0 @@
use crate::error::ContractError::{EvolveNotAllowed, OnlyOwnerCanEvolve};
use crate::state::{State};
use warp_wasm_utils::contract_utils::js_imports::Transaction;
use crate::action::ActionResult;
use warp_wasm_utils::contract_utils::handler_result::HandlerResult;
pub fn evolve(mut state: State, value: String) -> ActionResult {
match state.can_evolve {
Some(can_evolve) => if can_evolve && state.owner == Transaction::owner() {
state.evolve = Option::from(value);
Ok(HandlerResult::NewState(state))
} else {
Err(OnlyOwnerCanEvolve)
},
None => Err(EvolveNotAllowed),
}
}

View File

@@ -1,5 +0,0 @@
pub mod evolve;
pub mod balance;
pub mod transfers;
pub mod allowances;

View File

@@ -1,47 +0,0 @@
use crate::error::ContractError::{CallerBalanceNotEnough, CallerAllowanceNotEnough};
use crate::actions::allowances::{__set_allowance, __get_allowance};
use crate::state::State;
use crate::action::ActionResult;
use warp_wasm_utils::contract_utils::handler_result::HandlerResult;
use warp_wasm_utils::contract_utils::js_imports::{SmartWeave};
pub fn transfer(state: State, to: String, amount: u64) -> ActionResult {
let caller = SmartWeave::caller();
return _transfer(state, caller, to, amount);
}
pub fn transfer_from(mut state: State, from: String, to: String, amount: u64) -> ActionResult {
let caller = SmartWeave::caller();
//Checking allowance
let allowance = __get_allowance(&state.allowances, &from, &caller);
if allowance < amount {
return Err(CallerAllowanceNotEnough(allowance));
}
__set_allowance(&mut state.allowances, from.to_owned(), caller, allowance - amount);
return _transfer(state, from, to, amount);
}
fn _transfer(mut state: State, from: String, to: String, amount: u64) -> ActionResult {
// Checking if caller has enough funds
let balances = &mut state.balances;
let from_balance = *balances.get(&from).unwrap_or(&0);
if from_balance < amount {
return Err(CallerBalanceNotEnough(from_balance));
}
// Update caller balance or prune state if the new value is 0
if from_balance - amount == 0 {
balances.remove(&from);
} else {
balances.insert(from, from_balance - amount);
}
// Update target balance
*balances.entry(to).or_insert(0) += amount;
Ok(HandlerResult::NewState(state))
}

View File

@@ -1,38 +0,0 @@
use crate::action::{Action, ActionResult};
use crate::actions::transfers::transfer;
use crate::actions::transfers::transfer_from;
use crate::actions::balance::balance_of;
use crate::actions::balance::total_supply;
use crate::actions::allowances::approve;
use crate::actions::allowances::allowance;
use crate::actions::evolve::evolve;
use warp_wasm_utils::contract_utils::js_imports::{Block, Contract, log, SmartWeave, Transaction};
use crate::state::State;
pub async fn handle(current_state: State, action: Action) -> ActionResult {
//Example of accessing functions imported from js:
log("log from contract");
log(&("Transaction::id()".to_owned() + &Transaction::id()));
log(&("Transaction::owner()".to_owned() + &Transaction::owner()));
log(&("Transaction::target()".to_owned() + &Transaction::target()));
log(&("Block::height()".to_owned() + &Block::height().to_string()));
log(&("Block::indep_hash()".to_owned() + &Block::indep_hash()));
log(&("Block::timestamp()".to_owned() + &Block::timestamp().to_string()));
log(&("Contract::id()".to_owned() + &Contract::id()));
log(&("Contract::owner()".to_owned() + &Contract::owner()));
log(&("SmartWeave::caller()".to_owned() + &SmartWeave::caller()));
match action {
Action::Transfer { to, amount } => transfer(current_state, to, amount),
Action::TransferFrom { from, to, amount } => transfer_from(current_state, from, to, amount),
Action::BalanceOf { target } => balance_of(current_state, target),
Action::TotalSupply { } => total_supply(current_state),
Action::Approve { spender, amount } => approve(current_state, spender, amount),
Action::Allowance { owner, spender } => allowance(current_state, owner, spender),
Action::Evolve { value } => evolve(current_state, value),
}
}

View File

@@ -1,4 +0,0 @@
# contract_utils module
This is a module with boilerplate code for each SmartWeave RUST contract.
**Please don't modify it unless you 100% know what you are doing!**

View File

@@ -1,107 +0,0 @@
/////////////////////////////////////////////////////
/////////////// DO NOT MODIFY THIS FILE /////////////
/////////////////////////////////////////////////////
use std::cell::RefCell;
use serde_json::Error;
use wasm_bindgen::prelude::*;
use crate::action::{Action, QueryResponseMsg};
use crate::contract;
use warp_wasm_utils::contract_utils::handler_result::HandlerResult;
use crate::error::ContractError;
use crate::state::State;
/*
Note: in order do optimize communication between host and the WASM module,
we're storing the state inside the WASM module (for the time of state evaluation).
This allows to reduce the overhead of passing the state back and forth
between the host and module with each contract interaction.
In case of bigger states this overhead can be huge.
Same approach has been implemented for the AssemblyScript version.
So the flow (from the SDK perspective) is:
1. SDK calls exported WASM module function "initState" (with lastly cached state or initial state,
if cache is empty) - which initializes the state in the WASM module.
2. SDK calls "handle" function for each of the interaction.
If given interaction was modifying the state - it is updated inside the WASM module
- but not returned to host.
3. Whenever SDK needs to know the current state (eg. in order to perform
caching or to simply get its value after evaluating all of the interactions)
- it calls WASM's module "currentState" function.
The handle function by default does not return the new state -
it only updates it in the WASM module.
The handle function returns a value only in case of error
or calling a "view" function.
In the future this might also allow to enhance the inner-contracts communication
- e.g. if the execution network will store the state of the contracts - as the WASM contract module memory
- it would allow to read other contract's state "directly" from WASM module memory.
*/
// inspired by https://github.com/dfinity/examples/blob/master/rust/basic_dao/src/basic_dao/src/lib.rs#L13
thread_local! {
static STATE: RefCell<State> = RefCell::default();
}
#[wasm_bindgen()]
pub async fn handle(interaction: JsValue) -> Option<JsValue> {
let result: Result<HandlerResult<State, QueryResponseMsg>, ContractError>;
let action: Result<Action, Error> = interaction.into_serde();
if action.is_err() {
// cannot pass any data from action.error here - ends up with
// "FnOnce called more than once" error from wasm-bindgen for
// "foreign_call" testcase.
result = Err(ContractError::RuntimeError(
"Error while parsing input".to_string(),
));
} else {
// not sure about clone here
let current_state = STATE.with(|service| service.borrow().clone());
result = contract::handle(current_state, action.unwrap()).await;
}
if let Ok(HandlerResult::NewState(state)) = result {
STATE.with(|service| service.replace(state));
None
} else {
Some(JsValue::from_serde(&result).unwrap())
}
}
#[wasm_bindgen(js_name = initState)]
pub fn init_state(state: &JsValue) {
let state_parsed: State = state.into_serde().unwrap();
STATE.with(|service| service.replace(state_parsed));
}
#[wasm_bindgen(js_name = currentState)]
pub fn current_state() -> JsValue {
// not sure if that's deterministic - which is very important for the execution network.
// TODO: perf - according to docs:
// "This is unlikely to be super speedy so it's not recommended for large payload"
// - we should minimize calls to from_serde
let current_state = STATE.with(|service| service.borrow().clone());
JsValue::from_serde(&current_state).unwrap()
}
#[wasm_bindgen()]
pub fn version() -> i32 {
return 1;
}
// Workaround for now to simplify type reading without as/loader or wasm-bindgen
// 1 = assemblyscript
// 2 = rust
// 3 = go
// 4 = swift
// 5 = c
#[wasm_bindgen]
pub fn lang() -> i32 {
return 2;
}

View File

@@ -1,5 +0,0 @@
/////////////////////////////////////////////////////
/////////////// DO NOT MODIFY THIS FILE /////////////
/////////////////////////////////////////////////////
pub mod entrypoint;

View File

@@ -1,10 +0,0 @@
use serde::Serialize;
#[derive(Serialize)]
pub enum ContractError {
RuntimeError(String),
CallerBalanceNotEnough(u64),
CallerAllowanceNotEnough(u64),
OnlyOwnerCanEvolve,
EvolveNotAllowed
}

View File

@@ -1,6 +0,0 @@
mod state;
mod action;
mod error;
mod actions;
mod contract;
pub mod contract_utils;

View File

@@ -1,18 +0,0 @@
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct State {
pub symbol: String,
pub name: Option<String>,
pub decimals: u8,
pub total_supply: u64,
pub balances: HashMap<String, u64>,
pub allowances: HashMap<String, HashMap<String, u64>>,
//Evolve interface
pub owner: String,
pub evolve: Option<String>,
pub can_evolve: Option<bool>
}

View File

@@ -865,7 +865,7 @@ export class HandlerBasedContract<State> implements Contract<State> {
}
getUncommittedState(contractTxId: string): EvalStateResult<unknown> {
return (this.getRoot() as HandlerBasedContract<unknown>)._uncommittedStates.get(contractTxId);
return this.getRoot()._uncommittedStates.get(contractTxId);
}
setUncommittedState(contractTxId: string, result: EvalStateResult<unknown>): void {
@@ -883,6 +883,9 @@ export class HandlerBasedContract<State> implements Contract<State> {
async commitStates(interaction: GQLNodeInterface): Promise<void> {
const uncommittedStates = this.getRoot()._uncommittedStates;
try {
// i.e. if more than root contract state is in uncommitted state
// - without this check, we would effectively cache state for each evaluated interaction
// - which is not storage-effective
if (uncommittedStates.size > 1) {
for (const [k, v] of uncommittedStates) {
await this.warp.stateEvaluator.putInCache(k, interaction, v);

View File

@@ -65,6 +65,7 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
result.type !== 'ok' &&
effectiveThrowOnError &&
(!this.swGlobal._activeTx.dry || (this.swGlobal._activeTx.dry && this.swGlobal._activeTx.strict));
const effectiveErrorMessage = shouldAutoThrow
? `Internal write auto error for call [${JSON.stringify(debugData)}]: ${result.errorMessage}`
: result.errorMessage;
@@ -105,11 +106,7 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
};
}
protected assignReadContractState<Input>(
executionContext: ExecutionContext<State>,
currentResult: EvalStateResult<State>,
interactionTx: GQLNodeInterface
) {
protected assignReadContractState<Input>(executionContext: ExecutionContext<State>, interactionTx: GQLNodeInterface) {
this.swGlobal.contracts.readContractState = async (contractTxId: string, returnValidity?: boolean) => {
this.logger.debug('swGlobal.readContractState call:', {
from: this.contractDefinition.txId,
@@ -120,19 +117,6 @@ export abstract class AbstractContractHandler<State> implements HandlerApi<State
const { contract, warp } = executionContext;
/*let stateWithValidity;
if (!contract.isRoot() && contract.hasUncommittedState(contractTxId)) {
stateWithValidity = contract.getUncommittedState(contractTxId);
} else {
const childContract = warp.contract(contractTxId, contract, {
callingInteraction: interactionTx,
callType: 'read'
});
stateWithValidity = await childContract.readState(interactionTx.sortKey);
childContract.setUncommittedState(contractTxId, stateWithValidity);
}*/
const childContract = warp.contract(contractTxId, contract, {
callingInteraction: interactionTx,
callType: 'read'

View File

@@ -34,13 +34,12 @@ export class JsHandlerApi<State> extends AbstractContractHandler<State> {
const stateCopy = deepCopy(currentResult.state);
this.swGlobal._activeTx = interactionTx;
this.swGlobal.caller = interaction.caller; // either contract tx id (for internal writes) or transaction.owner
this.assignReadContractState<Input>(executionContext, currentResult, interactionTx);
this.assignReadContractState<Input>(executionContext, interactionTx);
this.assignViewContractState<Input>(executionContext);
this.assignWrite(executionContext);
this.assignRefreshState(executionContext);
const { warp } = executionContext;
await this.swGlobal.kv.open();
const handlerResult = await Promise.race([timeoutPromise, this.contractFunction(stateCopy, interaction)]);

View File

@@ -30,7 +30,7 @@ export class WasmHandlerApi<State> extends AbstractContractHandler<State> {
this.swGlobal.gasLimit = executionContext.evaluationOptions.gasLimit;
this.swGlobal.gasUsed = 0;
this.assignReadContractState<Input>(executionContext, currentResult, interactionTx);
this.assignReadContractState<Input>(executionContext, interactionTx);
this.assignViewContractState(executionContext);
this.assignWrite(executionContext);