From 2762daae950043244e7a9ef30c8e8ec17c5cfb6f Mon Sep 17 00:00:00 2001 From: NanezX Date: Wed, 8 Nov 2023 22:19:06 -0400 Subject: [PATCH] subgraph code base from OB sg repo --- subgraph/src/erc20.ts | 26 + subgraph/src/orderBook.ts | 1170 +++++++++++++++++++++++++++++++ subgraph/src/orderJsonString.ts | 138 ++++ subgraph/src/utils.ts | 384 ++++++++++ 4 files changed, 1718 insertions(+) create mode 100644 subgraph/src/erc20.ts create mode 100644 subgraph/src/orderBook.ts create mode 100644 subgraph/src/orderJsonString.ts create mode 100644 subgraph/src/utils.ts diff --git a/subgraph/src/erc20.ts b/subgraph/src/erc20.ts new file mode 100644 index 000000000..a0d8db882 --- /dev/null +++ b/subgraph/src/erc20.ts @@ -0,0 +1,26 @@ +import { Address } from "@graphprotocol/graph-ts"; +import { ReserveToken, Transfer } from "../generated/OrderBook/ReserveToken"; +import { ERC20 } from "../generated/schema"; +import { toDisplayWithDecimals } from "./utils"; + +export function handleTransfer(event: Transfer): void { + let token = ERC20.load(event.address.toHex()); + + if ( + token && + (event.params.from == Address.zero() || event.params.to == Address.zero()) + ) { + let reserveToken = ReserveToken.bind(Address.fromBytes(event.address)); + + let totalSupply = reserveToken.try_totalSupply(); + + if (!totalSupply.reverted) { + let value = totalSupply.value; + + token.totalSupply = value; + token.totalSupplyDisplay = toDisplayWithDecimals(value, token.decimals); + + token.save(); + } + } +} diff --git a/subgraph/src/orderBook.ts b/subgraph/src/orderBook.ts new file mode 100644 index 000000000..365f64b7f --- /dev/null +++ b/subgraph/src/orderBook.ts @@ -0,0 +1,1170 @@ +import { + Bounty, + IO, + ContentMetaV1, + Order, + OrderClearStateChange, + ClearOrderConfig, + TokenVault, + ContextEntity, + TokenVaultTakeOrder, + OrderBook, +} from "../generated/schema"; +import { + AddOrder, + AfterClear, + Clear, + Context, + Deposit, + MetaV1, + OrderExceedsMaxRatio, + OrderNotFound, + OrderZeroAmount, + RemoveOrder, + TakeOrder, + Withdraw, + // Initialized, + ClearAliceStruct, +} from "../generated/OrderBook/OrderBook"; +import { + BigDecimal, + BigInt, + Bytes, + JSONValue, + JSONValueKind, + TypedMap, + // ValueKind, + ethereum, + json, + // log, + // store, +} from "@graphprotocol/graph-ts"; + +import { + AFTER_CLEAR_EVENT_TOPIC, + CLEAR_EVENT_TOPIC, + NEW_EXPRESSION_EVENT_TOPIC, + RAIN_META_DOCUMENT_HEX, + TAKE_ORDER_EVENT_TOPIC, + createAccount, + createContextEntity, + createOrder, + createOrderClear, + createSignedContext, + createTakeOrderConfig, + createToken, + createTokenVault, + createTransaction, + createVault, + createVaultDeposit, + createVaultWithdraw, + getEvenHex, + getKeccak256FromBytes, + // getOB, + getRainMetaV1, + isHexadecimalString, + stringToArrayBuffer, + toDisplay, + tuplePrefix, +} from "./utils"; +import { CBORDecoder, CBOREncoder } from "@rainprotocol/assemblyscript-cbor"; +import { ExpressionJSONString, OrderString } from "./orderJsonString"; + +export function handleContext(event: Context): void { + const receipt = event.receipt; + + // Should be at least one log (the current event is one). This is conditional + // is just for safe typing. + if (receipt && receipt.logs.length > 0) { + const logs = receipt.logs; + + const log_takeOrder = logs.findIndex( + (log_) => log_.topics[0].toHex() == TAKE_ORDER_EVENT_TOPIC + ); + const log_clear = logs.findIndex( + (log_) => log_.topics[0].toHex() == CLEAR_EVENT_TOPIC + ); + const log_afterClear = logs.findIndex( + (log_) => log_.topics[0].toHex() == AFTER_CLEAR_EVENT_TOPIC + ); + + if (log_clear != -1 && log_afterClear != -1) { + // It's a context for clear and afterClear + // This is in case that need to support context for these events + } + + if (log_takeOrder != -1) { + // It's a context for a takeOrder + // Create the TakeOrder and assign the context entity ID. + // ON the takeOrder handler, the takeOrder entity should not be created, only + // read/obtained and modified with the data of the take order event. + const context = event.params.context; + + // Column 0 is the Context Base + const sender = Bytes.fromHexString(getEvenHex(context[0][0].toHex())); + const callerContract = Bytes.fromHexString( + getEvenHex(context[0][1].toHex()) + ); + + // Column 1 is the Context Calling + const callingContext = context[1]; + + // Column 2 is the Context Calculations + const calculationsContext = context[2]; + + // Column 3 is the Context Vault Inputs + const vaultInputsContext = context[3]; + + // Column 4 is the Context Vault Outputs + const vaultOutputsContext = context[4]; + + // After the column 4, all is of signedContext + const signedContextArr = context.slice(5); + + // Where all the "real" signed conext entities are "save" + const signedContextEntities: string[] = []; + + if (signedContextArr.length > 0) { + const signers = signedContextArr.shift(); + + if (signers.length == signedContextArr.length) { + for (let i = 0; i < signedContextArr.length; i++) { + const signedContextEntity = createSignedContext( + event.transaction.hash.toHex() + ); + + signedContextEntity.context = signedContextArr[i]; + signedContextEntity.signer = Bytes.fromHexString( + getEvenHex(signers[i].toHex()) + ); + + signedContextEntity.save(); + + signedContextEntities.push(signedContextEntity.id); + } + } + } + + // Temp + const contextTakeOrder = new ContextEntity("ContextTakeOrderTemp"); + + contextTakeOrder.caller = sender; + contextTakeOrder.callingContext = callingContext; + contextTakeOrder.calculationsContext = calculationsContext; + contextTakeOrder.vaultInputsContext = vaultInputsContext; + contextTakeOrder.vaultOutputsContext = vaultOutputsContext; + contextTakeOrder.signedContext = signedContextEntities; + + contextTakeOrder.emitter = createAccount(event.params.sender).id; + contextTakeOrder.timestamp = event.block.timestamp; + contextTakeOrder.transaction = createTransaction( + event.transaction.hash.toHex(), + event.block + ).id; + + contextTakeOrder.save(); + } + } +} + +export function handleAddOrder(event: AddOrder): void { + // Order parameter from event + const orderParam = event.params.order; + + const orderHashHex = getEvenHex(event.params.orderHash.toHex()); + + let order = new Order(orderHashHex); + + order.ordersClears = []; + order.transaction = createTransaction( + event.transaction.hash.toHex(), + event.block + ).id; + + order.orderHash = Bytes.fromHexString( + getEvenHex(event.params.orderHash.toHex()) + ); + order.timestamp = event.block.timestamp; + order.owner = createAccount(orderParam.owner).id; + order.emitter = createAccount(event.params.sender).id; + + order.expressionDeployer = event.params.expressionDeployer; + order.expression = orderParam.evaluable.expression; + order.interpreter = orderParam.evaluable.interpreter; + order.interpreterStore = orderParam.evaluable.store; + order.handleIO = orderParam.handleIO; + order.orderActive = true; + order.validInputs = []; + order.validOutputs = []; + + for (let i = 0; i < orderParam.validInputs.length; i++) { + let token = createToken(orderParam.validInputs[i].token); + let vault = createVault( + orderParam.validInputs[i].vaultId.toString(), + orderParam.owner + ); + let tokenVault = createTokenVault( + orderParam.validInputs[i].vaultId.toString(), + event.params.sender, + orderParam.validInputs[i].token + ); + + if (tokenVault) { + let orders = tokenVault.orders; + if (orders) orders.push(order.id); + tokenVault.orders = orders; + tokenVault.save(); + } + + let input = new IO( + `${orderHashHex}-${token.id}-${orderParam.validInputs[i].vaultId}` + ); + input.token = token.id; + input.decimals = orderParam.validInputs[i].decimals; + input.vault = vault.id; + input.vaultId = orderParam.validInputs[i].vaultId; + input.order = orderHashHex; + input.tokenVault = tokenVault.id; + input.index = i; + input.save(); + + // Add the input to the order entity + const auxInput = order.validInputs; + if (auxInput) if (!auxInput.includes(input.id)) auxInput.push(input.id); + order.validInputs = auxInput; + } + + for (let i = 0; i < orderParam.validOutputs.length; i++) { + let token = createToken(orderParam.validOutputs[i].token); + let vault = createVault( + orderParam.validOutputs[i].vaultId.toString(), + orderParam.owner + ); + let tokenVault = createTokenVault( + orderParam.validOutputs[i].vaultId.toString(), + event.params.sender, + orderParam.validOutputs[i].token + ); + + if (tokenVault) { + let orders = tokenVault.orders; + if (orders) orders.push(order.id); + tokenVault.orders = orders; + tokenVault.save(); + } + + let output = new IO( + `${orderHashHex}-${token.id}-${orderParam.validOutputs[i].vaultId}` + ); + output.token = token.id; + output.decimals = orderParam.validOutputs[i].decimals; + output.vault = vault.id; + output.vaultId = orderParam.validOutputs[i].vaultId; + output.order = orderHashHex; + output.tokenVault = tokenVault.id; + output.index = i; + output.save(); + + // Use the OrderString class to generate a Order JSON string compatible value + const orderString = new OrderString(orderParam); + order.orderJSONString = orderString.stringify(); + + // Add the input to the order entity + const auxOutput = order.validOutputs; + if (auxOutput) + if (!auxOutput.includes(output.id)) auxOutput.push(output.id); + + order.validOutputs = auxOutput; + } + + const receipt = event.receipt; + if (receipt && receipt.logs.length > 0) { + const logs = receipt.logs; + + const log_newExpression = logs.findIndex( + (log_) => log_.topics[0].toHex() == NEW_EXPRESSION_EVENT_TOPIC + ); + + if (log_newExpression != -1) { + const log_callerMeta = logs[log_newExpression]; + + const dataTuple = tuplePrefix.concat(log_callerMeta.data); + + const decodedData = ethereum.decode( + "(address,bytes,uint256[],uint256[])", + dataTuple + ); + + if (decodedData && decodedData.kind === ethereum.ValueKind.TUPLE) { + const newExpressionTuple = decodedData.toTuple(); + + const bytecode_ = newExpressionTuple[1].toBytes(); + const constants_ = newExpressionTuple[2].toBigIntArray(); + const minOutputs_ = newExpressionTuple[3].toBigIntArray(); + + const expressionJsonString = new ExpressionJSONString( + bytecode_, + constants_, + minOutputs_ + ); + order.expressionJSONString = expressionJsonString.stringify(); + } + } + } + + order.save(); +} + +export function handleAfterClear(event: AfterClear): void { + let config = ClearOrderConfig.load("1"); + const clearStateChange = event.params.clearStateChange; + + // Amounts + const aliceInput = clearStateChange.aliceInput; + const aliceOutput = clearStateChange.aliceOutput; + const bobInput = clearStateChange.bobInput; + const bobOutput = clearStateChange.bobOutput; + + // Bounty amounts + const bountyAmountA = aliceOutput.minus(bobInput); + const bountyAmountB = bobOutput.minus(aliceInput); + + if (config) { + let orderClearStateChange = new OrderClearStateChange(config.orderClearId); + orderClearStateChange.orderClear = config.orderClearId; + orderClearStateChange.aInput = aliceInput; + orderClearStateChange.aOutput = aliceOutput; + orderClearStateChange.bInput = bobInput; + orderClearStateChange.bOutput = bobOutput; + orderClearStateChange.save(); + + let bounty = Bounty.load(config.orderClearId); + if (bounty) { + bounty.bountyAmountA = bountyAmountA; + bounty.bountyAmountADisplay = toDisplay( + bountyAmountA, + bounty.bountyTokenA + ); + bounty.bountyAmountB = bountyAmountB; + bounty.bountyAmountBDisplay = toDisplay( + bountyAmountB, + bounty.bountyTokenB + ); + bounty.save(); + } + + if (bountyAmountA.gt(BigInt.zero())) { + const tokenVaultBounty_A = TokenVault.load(config.tokenVaultBountyAlice); + + if (tokenVaultBounty_A) { + tokenVaultBounty_A.balance = tokenVaultBounty_A.balance.plus( + bountyAmountA + ); + tokenVaultBounty_A.balanceDisplay = toDisplay( + tokenVaultBounty_A.balance, + tokenVaultBounty_A.token + ); + + tokenVaultBounty_A.save(); + } + } + + if (bountyAmountB.gt(BigInt.zero())) { + const tokenVaultBounty_B = TokenVault.load(config.tokenVaultBountyBob); + + if (tokenVaultBounty_B) { + tokenVaultBounty_B.balance = tokenVaultBounty_B.balance.plus( + bountyAmountB + ); + tokenVaultBounty_B.balanceDisplay = toDisplay( + tokenVaultBounty_B.balance, + tokenVaultBounty_B.token + ); + + tokenVaultBounty_B.save(); + } + } + + // TokenVaults IDs to update balance + const aliceTokenVaultInput_ID = config.aliceTokenVaultInput; + const aliceTokenVaultOutput_ID = config.aliceTokenVaultOutput; + const bobTokenVaultInput_ID = config.bobTokenVaultInput; + const bobTokenVaultOutput_ID = config.bobTokenVaultOutput; + + // Updating alice input/output balance + const aliceTokenVaultInput = TokenVault.load(aliceTokenVaultInput_ID); + if (aliceTokenVaultInput) { + aliceTokenVaultInput.balance = aliceTokenVaultInput.balance.plus( + aliceInput + ); + aliceTokenVaultInput.balanceDisplay = toDisplay( + aliceTokenVaultInput.balance, + aliceTokenVaultInput.token + ); + aliceTokenVaultInput.save(); + } + + const aliceTokenVaultOutput = TokenVault.load(aliceTokenVaultOutput_ID); + if (aliceTokenVaultOutput) { + aliceTokenVaultOutput.balance = aliceTokenVaultOutput.balance.minus( + aliceOutput + ); + aliceTokenVaultOutput.balanceDisplay = toDisplay( + aliceTokenVaultOutput.balance, + aliceTokenVaultOutput.token + ); + aliceTokenVaultOutput.save(); + } + + // Updating bob input/output balance + const bobTokenVaultInput = TokenVault.load(bobTokenVaultInput_ID); + if (bobTokenVaultInput) { + bobTokenVaultInput.balance = bobTokenVaultInput.balance.plus(bobInput); + bobTokenVaultInput.balanceDisplay = toDisplay( + bobTokenVaultInput.balance, + bobTokenVaultInput.token + ); + bobTokenVaultInput.save(); + } + + const bobTokenVaultOutput = TokenVault.load(bobTokenVaultOutput_ID); + if (bobTokenVaultOutput) { + bobTokenVaultOutput.balance = bobTokenVaultOutput.balance.minus( + bobOutput + ); + bobTokenVaultOutput.balanceDisplay = toDisplay( + bobTokenVaultOutput.balance, + bobTokenVaultOutput.token + ); + bobTokenVaultOutput.save(); + } + } +} + +export function handleClear(event: Clear): void { + const alice = event.params.alice; + const bob = event.params.bob; + const clearConfig = event.params.clearConfig; + const sender = event.params.sender; + + let orderClear = createOrderClear(event.transaction.hash.toHex()); + orderClear.sender = createAccount(sender).id; + orderClear.clearer = createAccount(sender).id; + + const order_A = createOrder(alice); + const order_B = createOrder(changetype(bob)); + + orderClear.orderA = order_A.id; + orderClear.orderB = order_B.id; + + // Saving order clears to each orders + // - ORDER A + const ordersClears_A = order_A.ordersClears; + if (ordersClears_A) ordersClears_A.push(orderClear.id); + order_A.ordersClears = ordersClears_A; + order_A.save(); + // - ORDER B + const ordersClears_B = order_B.ordersClears; + if (ordersClears_B) ordersClears_B.push(orderClear.id); + order_B.ordersClears = ordersClears_B; + order_B.save(); + + orderClear.owners = [ + createAccount(alice.owner).id, + createAccount(bob.owner).id, + ]; + orderClear.aInputIOIndex = clearConfig.aliceInputIOIndex; + orderClear.aOutputIOIndex = clearConfig.aliceOutputIOIndex; + orderClear.bInputIOIndex = clearConfig.bobInputIOIndex; + orderClear.bOutputIOIndex = clearConfig.bobOutputIOIndex; + orderClear.transaction = createTransaction( + event.transaction.hash.toHex(), + event.block + ).id; + orderClear.emitter = createAccount(event.params.sender).id; + orderClear.timestamp = event.block.timestamp; + orderClear.save(); + + let bounty = new Bounty(orderClear.id); + bounty.clearer = createAccount(sender).id; + bounty.orderClear = orderClear.id; + bounty.bountyVaultA = createVault( + clearConfig.aliceBountyVaultId.toString(), + sender + ).id; + bounty.bountyVaultB = createVault( + clearConfig.bobBountyVaultId.toString(), + sender + ).id; + + bounty.bountyTokenA = createToken( + alice.validOutputs[clearConfig.aliceOutputIOIndex.toI32()].token + ).id; + bounty.bountyTokenB = createToken( + bob.validOutputs[clearConfig.bobOutputIOIndex.toI32()].token + ).id; + bounty.transaction = createTransaction( + event.transaction.hash.toHex(), + event.block + ).id; + bounty.emitter = createAccount(event.params.sender).id; + bounty.timestamp = event.block.timestamp; + bounty.save(); + + // IO Index values used to clear (for alice and bob) + const aliceInputIOIndex = clearConfig.aliceInputIOIndex.toI32(); + const aliceOutputIOIndex = clearConfig.aliceOutputIOIndex.toI32(); + const bobInputIOIndex = clearConfig.bobInputIOIndex.toI32(); + const bobOutputIOIndex = clearConfig.bobOutputIOIndex.toI32(); + + // Valid inputs/outpus based on the Index used (for alice and bob) + const aliceInputValues = alice.validInputs[aliceInputIOIndex]; + const aliceOutputValues = alice.validOutputs[aliceOutputIOIndex]; + const bobInputValues = bob.validInputs[bobInputIOIndex]; + const bobOutputValues = bob.validOutputs[bobOutputIOIndex]; + + // Token input/output based on the Index used (for alice and bob) + const aliceTokenInput = aliceInputValues.token; + const aliceTokenOutput = aliceOutputValues.token; + const bobTokenInput = bobInputValues.token; + const bobTokenOutput = bobOutputValues.token; + + // Vault IDs input/output based on the Index used (for alice and bob) + const aliceVaultInput = aliceInputValues.vaultId; + const aliceVaultOutput = aliceOutputValues.vaultId; + const bobVaultInput = bobInputValues.vaultId; + const bobVaultOutput = bobOutputValues.vaultId; + + // Token Vault IDs to use + const aliceTokenVaultInput = `${aliceVaultInput.toString()}-${alice.owner.toHex()}-${aliceTokenInput.toHex()}`; + const aliceTokenVaultOutput = `${aliceVaultOutput.toString()}-${alice.owner.toHex()}-${aliceTokenOutput.toHex()}`; + const bobTokenVaultInput = `${bobVaultInput.toString()}-${bob.owner.toHex()}-${bobTokenInput.toHex()}`; + const bobTokenVaultOutput = `${bobVaultOutput.toString()}-${bob.owner.toHex()}-${bobTokenOutput.toHex()}`; + + // Only should link the TokenVault to OrderClear where they are being clear + // using a the given vault and token. + // Only will link to four (4) token vaults this clear. Does not care if the + // orders have more valid inputs/outputs. + const tokenVaultArr = [ + aliceTokenVaultInput, + aliceTokenVaultOutput, + bobTokenVaultInput, + bobTokenVaultOutput, + ]; + + // The token vault should exist on this point since it was created when + // the order was added. + for (let i = 0; i < 4; i++) { + const tokenVault_ID = tokenVaultArr[i]; + let tokenVault = TokenVault.load(tokenVault_ID); + if (tokenVault) { + let orderClears = tokenVault.orderClears; + if (orderClears) orderClears.push(orderClear.id); + tokenVault.orderClears = orderClears; + tokenVault.save(); + } + } + + // Clearer/Bounty address token vaults + let tokenVaultBountyAlice = createTokenVault( + event.params.clearConfig.aliceBountyVaultId.toString(), + event.params.sender, + event.params.alice.validOutputs[ + event.params.clearConfig.aliceOutputIOIndex.toI32() + ].token + ); + + let tokenVaultBountyBob = createTokenVault( + event.params.clearConfig.bobBountyVaultId.toString(), + event.params.sender, + event.params.bob.validOutputs[ + event.params.clearConfig.bobOutputIOIndex.toI32() + ].token + ); + + let config = new ClearOrderConfig("1"); + config.orderClearId = orderClear.id; + config.tokenVaultBountyAlice = tokenVaultBountyAlice.id; + config.tokenVaultBountyBob = tokenVaultBountyBob.id; + config.aliceTokenVaultInput = aliceTokenVaultInput; + config.aliceTokenVaultOutput = aliceTokenVaultOutput; + config.bobTokenVaultInput = bobTokenVaultInput; + config.bobTokenVaultOutput = bobTokenVaultOutput; + config.save(); +} + +export function handleDeposit(event: Deposit): void { + let tokenVault = createTokenVault( + event.params.vaultId.toString(), + event.params.sender, + event.params.token + ); + + if (tokenVault) { + tokenVault.balance = tokenVault.balance.plus(event.params.amount); + tokenVault.balanceDisplay = toDisplay( + tokenVault.balance, + event.params.token.toHexString() + ); + tokenVault.save(); + } + + let vaultDeposit = createVaultDeposit(event.transaction.hash.toHex()); + vaultDeposit.sender = createAccount(event.params.sender).id; + vaultDeposit.token = createToken(event.params.token).id; + vaultDeposit.vaultId = event.params.vaultId; + vaultDeposit.vault = createVault( + event.params.vaultId.toString(), + event.params.sender + ).id; + vaultDeposit.amount = event.params.amount; + vaultDeposit.amountDisplay = toDisplay( + vaultDeposit.amount, + event.params.token.toHexString() + ); + vaultDeposit.tokenVault = tokenVault.id; + vaultDeposit.vault = createVault( + event.params.vaultId.toString(), + event.params.sender + ).id; + vaultDeposit.transaction = createTransaction( + event.transaction.hash.toHex(), + event.block + ).id; + vaultDeposit.emitter = createAccount(event.params.sender).id; + vaultDeposit.timestamp = event.block.timestamp; + vaultDeposit.save(); +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function handleOrderExceedsMaxRatio(event: OrderExceedsMaxRatio): void { + // +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function handleOrderNotFound(event: OrderNotFound): void { + // +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +export function handleOrderZeroAmount(event: OrderZeroAmount): void { + // +} + +export function handleRemoveOrder(event: RemoveOrder): void { + const orderHashHex = getEvenHex(event.params.orderHash.toHex()); + + let order = Order.load(orderHashHex); + if (order) { + order.orderActive = false; + order.save(); + } +} + +export function handleTakeOrder(event: TakeOrder): void { + let takeOrderEntity = createTakeOrderConfig(event.transaction.hash.toHex()); + takeOrderEntity.sender = createAccount(event.params.sender).id; + takeOrderEntity.order = createOrder( + changetype(event.params.config.order) + ).id; + + takeOrderEntity.input = event.params.input; + takeOrderEntity.inputDisplay = toDisplay( + event.params.input, + event.params.config.order.validOutputs[ + event.params.config.outputIOIndex.toI32() + ].token.toHexString() + ); + + takeOrderEntity.output = event.params.output; + takeOrderEntity.outputDisplay = toDisplay( + event.params.output, + event.params.config.order.validInputs[ + event.params.config.inputIOIndex.toI32() + ].token.toHexString() + ); + if (takeOrderEntity.outputDisplay != BigDecimal.zero()) { + takeOrderEntity.IORatio = takeOrderEntity.inputDisplay.div( + takeOrderEntity.outputDisplay + ); + } else { + takeOrderEntity.IORatio = BigDecimal.zero(); + } + + takeOrderEntity.inputIOIndex = event.params.config.inputIOIndex; + takeOrderEntity.outputIOIndex = event.params.config.outputIOIndex; + + takeOrderEntity.inputToken = createToken( + event.params.config.order.validOutputs[ + event.params.config.outputIOIndex.toI32() + ].token + ).id; + + takeOrderEntity.outputToken = createToken( + event.params.config.order.validInputs[ + event.params.config.inputIOIndex.toI32() + ].token + ).id; + + takeOrderEntity.transaction = createTransaction( + event.transaction.hash.toHex(), + event.block + ).id; + takeOrderEntity.emitter = createAccount(event.params.sender).id; + takeOrderEntity.timestamp = event.block.timestamp; + + // Adding the context + const contextTakeOrder = ContextEntity.load("ContextTakeOrderTemp"); + if (contextTakeOrder) { + const contextEntity = createContextEntity(event.transaction.hash.toHex()); + + contextEntity.caller = contextTakeOrder.caller; + contextEntity.callingContext = contextTakeOrder.callingContext; + contextEntity.calculationsContext = contextTakeOrder.calculationsContext; + contextEntity.vaultInputsContext = contextTakeOrder.vaultInputsContext; + contextEntity.vaultOutputsContext = contextTakeOrder.vaultOutputsContext; + contextEntity.signedContext = contextTakeOrder.signedContext; + + contextEntity.emitter = contextTakeOrder.emitter; + contextEntity.timestamp = contextTakeOrder.timestamp; + contextEntity.transaction = contextTakeOrder.transaction; + + takeOrderEntity.context = contextEntity.id; + + contextEntity.save(); + + // store.remove("ContextEntity", "ContextTakeOrderTemp"); + } + + takeOrderEntity.save(); + + // Updating Balance + + let order = event.params.config.order; + + // IO Index values used to takeOrder + const inputIOIndex = event.params.config.inputIOIndex.toI32(); + const outputIOIndex = event.params.config.outputIOIndex.toI32(); + + // Valid inputs/outpus based on the Index used + const inputValues = order.validInputs[inputIOIndex]; + const outputValues = order.validOutputs[outputIOIndex]; + + // Token input/output based on the Index used + const tokenInput = inputValues.token; + const tokenOutput = outputValues.token; + + // Vault IDs input/output based on the Index used + const vaultInput = inputValues.vaultId; + const vaultOutput = outputValues.vaultId; + + const tokenVaultInput = `${vaultInput.toString()}-${order.owner.toHex()}-${tokenInput.toHex()}`; + const tokenVaultOutput = `${vaultOutput.toString()}-${order.owner.toHex()}-${tokenOutput.toHex()}`; + + // Updating order input/output balance + const orderTokenVaultInput = TokenVault.load(tokenVaultInput); + if (orderTokenVaultInput) { + orderTokenVaultInput.balance = orderTokenVaultInput.balance.plus( + event.params.output + ); + orderTokenVaultInput.balanceDisplay = toDisplay( + orderTokenVaultInput.balance, + orderTokenVaultInput.token + ); + orderTokenVaultInput.save(); + + let takeOrderTokenVault = TokenVaultTakeOrder.load( + `${takeOrderEntity.id}-${orderTokenVaultInput.id}` + ); + if (!takeOrderTokenVault) { + takeOrderTokenVault = new TokenVaultTakeOrder( + `${takeOrderEntity.id}-${orderTokenVaultInput.id}` + ); + takeOrderTokenVault.wasInput = true; + takeOrderTokenVault.wasOutput = false; + takeOrderTokenVault.takeOrder = takeOrderEntity.id; + takeOrderTokenVault.tokenVault = orderTokenVaultInput.id; + takeOrderTokenVault.save(); + } + } + + // Updating order input/output balance + const orderTokenVaultOutput = TokenVault.load(tokenVaultOutput); + if (orderTokenVaultOutput) { + orderTokenVaultOutput.balance = orderTokenVaultOutput.balance.minus( + event.params.input + ); + orderTokenVaultOutput.balanceDisplay = toDisplay( + orderTokenVaultOutput.balance, + orderTokenVaultOutput.token + ); + orderTokenVaultOutput.save(); + + let takeOrderTokenVault = TokenVaultTakeOrder.load( + `${takeOrderEntity.id}-${orderTokenVaultOutput.id}` + ); + if (!takeOrderTokenVault) { + takeOrderTokenVault = new TokenVaultTakeOrder( + `${takeOrderEntity.id}-${orderTokenVaultOutput.id}` + ); + takeOrderTokenVault.wasInput = false; + takeOrderTokenVault.wasOutput = true; + takeOrderTokenVault.takeOrder = takeOrderEntity.id; + takeOrderTokenVault.tokenVault = orderTokenVaultOutput.id; + takeOrderTokenVault.save(); + } + } +} + +export function handleWithdraw(event: Withdraw): void { + let tokenVault = createTokenVault( + event.params.vaultId.toString(), + event.params.sender, + event.params.token + ); + + if (tokenVault) { + tokenVault.balance = tokenVault.balance.minus(event.params.amount); + tokenVault.balanceDisplay = toDisplay( + tokenVault.balance, + event.params.token.toHexString() + ); + tokenVault.save(); + } + + let vaultWithdraw = createVaultWithdraw(event.transaction.hash.toHex()); + vaultWithdraw.sender = createAccount(event.params.sender).id; + vaultWithdraw.token = createToken(event.params.token).id; + vaultWithdraw.vaultId = event.params.vaultId; + vaultWithdraw.vault = createVault( + event.params.vaultId.toString(), + event.params.sender + ).id; + vaultWithdraw.requestedAmount = event.params.targetAmount; + vaultWithdraw.requestedAmountDisplay = toDisplay( + event.params.targetAmount, + event.params.token.toHexString() + ); + vaultWithdraw.amount = event.params.amount; + vaultWithdraw.amountDisplay = toDisplay( + vaultWithdraw.amount, + event.params.token.toHexString() + ); + vaultWithdraw.tokenVault = tokenVault.id; + vaultWithdraw.vault = createVault( + event.params.vaultId.toString(), + event.params.sender + ).id; + vaultWithdraw.transaction = createTransaction( + event.transaction.hash.toHex(), + event.block + ).id; + vaultWithdraw.emitter = createAccount(event.params.sender).id; + vaultWithdraw.timestamp = event.block.timestamp; + vaultWithdraw.save(); +} + +export function handleMetaV1(event: MetaV1): void { + const metaV1 = getRainMetaV1(event.params.meta); + + const subjectHex = getEvenHex(event.params.subject.toHex()); + + // If the subject is equal to the emiter address, then the meta is for the OB. + // In this scenario, it is considered that is from DeployerDiscoverableMeta. + if (subjectHex == event.address.toHex()) { + let orderBook = OrderBook.load(event.address); + if (!orderBook) { + orderBook = new OrderBook(event.address); + orderBook.deployer = event.transaction.from; + orderBook.address = event.address; + orderBook.meta = metaV1.id; + + orderBook.save(); + } + } else { + // If not, the subject is an OrderHash then it's an Order meta + const orderHash = getEvenHex(event.params.subject.toHex()); + const order = Order.load(orderHash); + if (order) { + order.meta = metaV1.id; + order.save(); + } + } + + // Converts the emitted target from Bytes to a Hexadecimal value + let meta = event.params.meta.toHex(); + + // Decode the meta only if incluse the RainMeta magic number. + if (meta.includes(RAIN_META_DOCUMENT_HEX)) { + meta = meta.replace(RAIN_META_DOCUMENT_HEX, ""); + const data = new CBORDecoder(stringToArrayBuffer(meta)); + const res = data.parse(); + + // MetaV1.content + const auxContent = metaV1.content; + + const contentArr: ContentMeta[] = []; + + if (res.isSequence) { + const dataString = res.toString(); + const jsonArr = json.fromString(dataString).toArray(); + for (let i = 0; i < jsonArr.length; i++) { + const jsonValue = jsonArr[i]; + + // if some value is not a JSON/Map, then is not following the RainMeta design. + // So, return here to avoid assignation. + if (jsonValue.kind != JSONValueKind.OBJECT) return; + + const jsonContent = jsonValue.toObject(); + + // If some content is not valid, then skip it since is bad formed + if (!ContentMeta.validate(jsonContent)) return; + + const content = new ContentMeta(jsonContent, metaV1.id); + contentArr.push(content); + } + } else if (res.isObj) { + const dataString = res.toString(); + const jsonObj = json.fromString(dataString).toObject(); + + if (!ContentMeta.validate(jsonObj)) return; + const content = new ContentMeta(jsonObj, metaV1.id); + contentArr.push(content); + // + } else { + // If the response is NOT a Sequence or an Object, then the meta have an + // error or it's bad formed. + // In this case, we skip to continue the decoding and assignation process. + return; + } + + for (let i = 0; i < contentArr.length; i++) { + const metaContent_ = contentArr[i].generate(event.address.toHex()); + + // This include each meta content on the RainMeta related + if (!auxContent.includes(metaContent_.id)) { + auxContent.push(metaContent_.id); + } + } + + // Saving + for (let i = 0; i < contentArr.length; i++) { + contentArr[i].saveMeta(); + } + + metaV1.content = auxContent; + metaV1.save(); + } else { + // The meta emitted does not include the RainMeta magic number, so does not + // follow the RainMeta Desing + return; + } +} +export class ContentMeta { + rainMetaId: Bytes; + encodedData: Bytes = Bytes.empty(); + payload: Bytes = Bytes.empty(); + // eslint-disable-next-line @typescript-eslint/ban-types + magicNumber: BigInt = BigInt.zero(); + contentType: string = ""; + contentEncoding: string = ""; + contentLanguage: string = ""; + + private contentTypeAdded: boolean = false; + private contentEncodingAdded: boolean = false; + private contentLanguageAdded: boolean = false; + + private metaContent: ContentMetaV1 = new ContentMetaV1(Bytes.empty()); + private metaStored: boolean = false; + + constructor( + metaContentV1Object_: TypedMap, + rainMetaID_: Bytes + ) { + const payload = metaContentV1Object_.get("0"); + const magicNumber = metaContentV1Object_.get("1"); + const contentType = metaContentV1Object_.get("2"); + const contentEncoding = metaContentV1Object_.get("3"); + const contentLanguage = metaContentV1Object_.get("4"); + + // RainMetaV1 ID + this.rainMetaId = rainMetaID_; + + // Mandatories keys + if (payload) { + let auxPayload = payload.toString(); + if (auxPayload.startsWith("h'")) { + auxPayload = auxPayload.replace("h'", ""); + } + if (auxPayload.endsWith("'")) { + auxPayload = auxPayload.replace("'", ""); + } + + this.payload = Bytes.fromHexString(auxPayload); + } + + // if (payload) this.payload = payload.toString(); + if (magicNumber) this.magicNumber = magicNumber.toBigInt(); + + // Keys optionals + if (contentType) { + this.contentTypeAdded = true; + this.contentType = contentType.toString(); + } + + if (contentEncoding) { + this.contentEncodingAdded = true; + this.contentEncoding = contentEncoding.toString(); + } + + if (contentLanguage) { + this.contentLanguageAdded = true; + this.contentLanguage = contentLanguage.toString(); + } + } + + /** + * Validate that the keys exist on the map + */ + static validate(metaContentV1Object: TypedMap): boolean { + const payload = metaContentV1Object.get("0"); + const magicNumber = metaContentV1Object.get("1"); + const contentType = metaContentV1Object.get("2"); + const contentEncoding = metaContentV1Object.get("3"); + const contentLanguage = metaContentV1Object.get("4"); + + // Only payload and magicNumber are mandatory on RainMetaV1 + // See: https://github.com/rainprotocol/specs/blob/main/metadata-v1.md + if (payload && magicNumber) { + if ( + payload.kind == JSONValueKind.STRING || + magicNumber.kind == JSONValueKind.NUMBER + ) { + // Check if payload is a valid Bytes (hexa) + let auxPayload = payload.toString(); + if (auxPayload.startsWith("h'")) { + auxPayload = auxPayload.replace("h'", ""); + } + if (auxPayload.endsWith("'")) { + auxPayload = auxPayload.replace("'", ""); + } + + // If the payload is not a valid bytes value + if (!isHexadecimalString(auxPayload)) { + return false; + } + + // Check the type of optionals keys + if (contentType) { + if (contentType.kind != JSONValueKind.STRING) { + return false; + } + } + if (contentEncoding) { + if (contentEncoding.kind != JSONValueKind.STRING) { + return false; + } + } + if (contentLanguage) { + if (contentLanguage.kind != JSONValueKind.STRING) { + return false; + } + } + + return true; + } + } + + return false; + } + + private getContentId(): Bytes { + // Values as Bytes + const encoder = new CBOREncoder(); + // Initially, the map always have two keys/values (payload and magic number) + let mapLength = 2; + + if (this.contentTypeAdded) mapLength += 1; + if (this.contentEncodingAdded) mapLength += 1; + if (this.contentLanguageAdded) mapLength += 1; + + encoder.addObject(mapLength); + + // -- Add key 0 (payload) + encoder.addUint8(0); + encoder.addBytes(this.payload); + + // -- Add key 1 (magic number) + encoder.addUint8(1); + encoder.addUint64(this.magicNumber.toU64()); + + if (this.contentTypeAdded) { + // -- Add key 2 (Content-Type) + encoder.addUint8(2); + encoder.addString(this.contentType); + } + + if (this.contentEncodingAdded) { + // -- Add key 3 (Content-Encoding) + encoder.addUint8(3); + encoder.addString(this.contentEncoding); + } + + if (this.contentLanguageAdded) { + // -- Add key 4 (Content-Language) + encoder.addUint8(4); + encoder.addString(this.contentLanguage); + } + + this.encodedData = Bytes.fromHexString(encoder.serializeString()); + + return getKeccak256FromBytes(this.encodedData); + } + + /** + * Create or generate a ContentMetaV1 entity based on the current fields: + * + * - If the ContentMetaV1 does not exist, create the ContentMetaV1 entity and + * made the relation to the rainMetaId. + * + * - If the ContentMetaV1 does exist, add the relation to the rainMetaId. + */ + generate(addressID: string): ContentMetaV1 { + const contentId = this.getContentId(); + + let metaContent = ContentMetaV1.load(contentId); + + if (!metaContent) { + metaContent = new ContentMetaV1(contentId); + + metaContent.rawBytes = this.encodedData; + metaContent.magicNumber = this.magicNumber; + metaContent.payload = this.payload; + metaContent.parents = []; + + if (this.contentType != "") metaContent.contentType = this.contentType; + + if (this.contentEncoding != "") + metaContent.contentEncoding = this.contentEncoding; + + if (this.contentLanguage != "") + metaContent.contentLanguage = this.contentLanguage; + } + + const auxParents = metaContent.parents; + if (!auxParents.includes(this.rainMetaId)) auxParents.push(this.rainMetaId); + metaContent.parents = auxParents; + + this.metaContent = metaContent; + this.metaStored = true; + // metaContent.save(); + + return this.metaContent; + } + + saveMeta(): void { + if (this.metaStored && this.metaContent.id.notEqual(Bytes.empty())) { + this.metaContent.save(); + } + } +} diff --git a/subgraph/src/orderJsonString.ts b/subgraph/src/orderJsonString.ts new file mode 100644 index 000000000..6a213632f --- /dev/null +++ b/subgraph/src/orderJsonString.ts @@ -0,0 +1,138 @@ +import { Address, BigInt, Bytes, log } from "@graphprotocol/graph-ts"; +import { AddOrderOrderStruct } from "../generated/OrderBook/OrderBook"; +import { getEvenHex } from "./utils"; + +class JsonString { + _obj: Map; + + constructor(map_: Map) { + this._obj = map_; + } + + stringify(): string { + const keys = this._obj.keys(); + const objs: string[] = new Array(keys.length); + + for (let i: i32 = 0; i < keys.length; i++) { + const key = keys[i]; + const value = this._obj.get(key); + // "Array" + if (value.startsWith("[") && value.endsWith("]")) { + // + objs[i] = `"${key}":${value}`; + } else { + objs[i] = `"${key}":"${value}"`; + } + } + + return `{${objs.join(",")}}`; + } +} + +/** + * Generate a JSON string for a given order to be ready to use with tools + */ + +export class OrderString extends JsonString { + constructor(order_: AddOrderOrderStruct) { + const _map: Map = new Map(); + + const evaluable_ = new Evaluable_String( + order_.evaluable.interpreter, + order_.evaluable.store, + order_.evaluable.expression + ); + + const validInputsArr: string[] = []; + const validOutputsArr: string[] = []; + + const validInputs_ = order_.validInputs; + const validOutputs_ = order_.validOutputs; + + for (let i = 0; i < validInputs_.length; i++) { + const input_ = validInputs_[i]; + const io_ = new IO_String(input_.token, input_.decimals, input_.vaultId); + + validInputsArr.push(io_.stringify()); + } + + for (let i = 0; i < validOutputs_.length; i++) { + const output_ = validOutputs_[i]; + const io_ = new IO_String( + output_.token, + output_.decimals, + output_.vaultId + ); + + validOutputsArr.push(io_.stringify()); + } + + _map.set("owner", getEvenHex(order_.owner.toHex())); + _map.set("handleIo", (order_.handleIO as bool).toString()); + _map.set("evaluable", evaluable_.stringify()); + _map.set("validInputs", `[${validInputsArr.join(",")}]`); + _map.set("validOutputs", `[${validOutputsArr.join(",")}]`); + + super(_map); + } + + stringify(): string { + const keys = this._obj.keys(); + const objs: string[] = new Array(keys.length); + + for (let i: i32 = 0; i < keys.length; i++) { + const key = keys[i]; + const value = this._obj.get(key); + if (key == "owner") { + objs[i] = `"${key}":"${value}"`; + } else { + objs[i] = `"${key}":${value}`; + } + } + + return `{${objs.join(",")}}`; + } +} + +class IO_String extends JsonString { + constructor(token_: Address, decimals_: number, vaultId_: BigInt) { + const _map: Map = new Map(); + + _map.set("token", getEvenHex(token_.toHex())); + _map.set("decimals", decimals_.toString().split(".")[0]); + _map.set("vaultId", vaultId_.toHex()); + + super(_map); + } +} + +class Evaluable_String extends JsonString { + constructor(interpreter_: Address, store_: Address, expression_: Address) { + const _map: Map = new Map(); + + _map.set("interpreter", getEvenHex(interpreter_.toHex())); + _map.set("store", getEvenHex(store_.toHex())); + _map.set("expression", getEvenHex(expression_.toHex())); + + super(_map); + } +} + +export class ExpressionJSONString extends JsonString { + constructor(bytecode_: Bytes, constants_: BigInt[], minOutputs_: BigInt[]) { + const _map: Map = new Map(); + + const minOutputs_string = minOutputs_.map( + (x): string => `"${x.toHexString()}"` + ); + const constants_string = constants_.map( + (x): string => `"${x.toHexString()}"` + ); + + _map.set("bytecode", bytecode_.toHexString()); + _map.set("constants", `[${constants_string.join(",")}]`); + _map.set("minOutputs", `[${minOutputs_string.join(",")}]`); + + super(_map); + } +} diff --git a/subgraph/src/utils.ts b/subgraph/src/utils.ts new file mode 100644 index 000000000..3d0099fe5 --- /dev/null +++ b/subgraph/src/utils.ts @@ -0,0 +1,384 @@ +import { + Bytes, + BigInt, + Address, + ethereum, + crypto, + BigDecimal, +} from "@graphprotocol/graph-ts"; +import { + Account, + ContextEntity, + ERC20, + Order, + OrderBook, + OrderClear, + RainMetaV1, + SignedContext, + TakeOrderEntity, + TokenVault, + Transaction, + Vault, + VaultDeposit, + VaultWithdraw, +} from "../generated/schema"; +import { ReserveToken } from "../generated/OrderBook/ReserveToken"; +import { ClearAliceStruct } from "../generated/OrderBook/OrderBook"; + +export const RAIN_META_DOCUMENT_HEX = "0xff0a89c674ee7874"; + +// Orderbook: TakeOrder(address sender, TakeOrderConfig config, uint256 input, uint256 output) +export let TAKE_ORDER_EVENT_TOPIC = + "0x219a030b7ae56e7bea2baab709a4a45dc174a1f85e57730e5cb395bc32962542"; + +// Orderbook: Clear(address sender, Order alice, Order bob, ClearConfig clearConfig) +export let CLEAR_EVENT_TOPIC = + "0xd153812deb929a6e4378f6f8cf61d010470840bf2e736f43fb2275803958bfa2"; + +// Orderbook: AfterClear(address sender, ClearStateChange clearStateChange); +export let AFTER_CLEAR_EVENT_TOPIC = + "0x3f20e55919cca701abb2a40ab72542b25ea7eed63a50f979dd2cd3231e5f488d"; + +// ExpressionDeployer: NewExpression(address,bytes,uint256[],uint256[]) +export let NEW_EXPRESSION_EVENT_TOPIC = + "0x4a48f556905d90b4a58742999556994182322843167010b59bf8149724db51cf"; + +export const tuplePrefix = Bytes.fromHexString( + "0000000000000000000000000000000000000000000000000000000000000020" +); + +/** + * From a given hexadecimal string, check if it's have an even length + */ +export function getEvenHex(value_: string): string { + if (value_.length % 2) { + value_ = value_.slice(0, 2) + "0" + value_.slice(2); + } + + return value_; +} + +export function stringToArrayBuffer(val: string): ArrayBuffer { + const buff = new ArrayBuffer(val.length / 2); + const view = new DataView(buff); + for (let i = 0, j = 0; i < val.length; i = i + 2, j++) { + view.setUint8(j, u8(Number.parseInt(`${val.at(i)}${val.at(i + 1)}`, 16))); + } + return buff; +} + +export function createAccount(address: Bytes): Account { + let account = Account.load(address); + if (!account) { + account = new Account(address); + account.save(); + } + return account; +} + +export function createToken(address: Bytes): ERC20 { + let token = ERC20.load(address.toHex()); + let reserveToken = ReserveToken.bind(Address.fromBytes(address)); + if (!token) { + token = new ERC20(address.toHex()); + + let decimals = reserveToken.try_decimals(); + let name = reserveToken.try_name(); + let symbol = reserveToken.try_symbol(); + let totalSupply = reserveToken.try_totalSupply(); + + token.decimals = !decimals.reverted ? decimals.value : 0; + token.name = !name.reverted ? name.value : "NONE"; + token.symbol = !symbol.reverted ? symbol.value : "NONE"; + token.totalSupply = !totalSupply.reverted + ? totalSupply.value + : BigInt.zero(); + + if (!totalSupply.reverted && !decimals.reverted) { + token.totalSupplyDisplay = toDisplayWithDecimals( + totalSupply.value, + decimals.value + ); + } else { + token.totalSupplyDisplay = BigDecimal.zero(); + } + + token.save(); + } + // else { + // let totalSupply = reserveToken.try_totalSupply(); + // if (!totalSupply.reverted) { + // let value = totalSupply.value; + // token.totalSupply = value; + // token.totalSupplyDisplay = toDisplayWithDecimals(value, token.decimals); + // } + // } + + return token; +} + +export function createVault(vaultId: string, owner: Bytes): Vault { + let vault = Vault.load(`${vaultId}-${owner.toHex()}`); + if (!vault) { + vault = new Vault(`${vaultId}-${owner.toHex()}`); + vault.owner = createAccount(owner).id; + vault.vaultId = BigInt.fromString(vaultId); + vault.save(); + } + return vault; +} + +export function createTokenVault( + vaultId: string, + owner: Bytes, + token: Bytes +): TokenVault { + let tokenVault = TokenVault.load( + `${vaultId}-${owner.toHex()}-${token.toHex()}` + ); + if (!tokenVault) { + tokenVault = new TokenVault(`${vaultId}-${owner.toHex()}-${token.toHex()}`); + tokenVault.owner = createAccount(owner).id; + tokenVault.token = createToken(token).id; + tokenVault.balance = BigInt.zero(); + tokenVault.balanceDisplay = BigDecimal.zero(); + tokenVault.vault = createVault(vaultId, owner).id; + tokenVault.vaultId = BigInt.fromString(vaultId); + tokenVault.orders = []; + tokenVault.orderClears = []; + tokenVault.save(); + } + return tokenVault; +} + +export function createOrder(order: ClearAliceStruct): Order { + let tupleEvaluable: Array = [ + ethereum.Value.fromAddress(order.evaluable.interpreter), + ethereum.Value.fromAddress(order.evaluable.store), + ethereum.Value.fromAddress(order.evaluable.expression), + ]; + + let evaluable = changetype(tupleEvaluable); + + let tupleValidInputs: Array = []; + for (let i = 0; i < order.validInputs.length; i++) { + let VI: Array = [ + ethereum.Value.fromAddress(order.validInputs[i].token), + ethereum.Value.fromI32(order.validInputs[i].decimals), + ethereum.Value.fromUnsignedBigInt(order.validInputs[i].vaultId), + ]; + + tupleValidInputs.push(changetype(VI)); + } + + let tupleValidOutputs: Array = []; + for (let i = 0; i < order.validOutputs.length; i++) { + let VO: Array = [ + ethereum.Value.fromAddress(order.validOutputs[i].token), + ethereum.Value.fromI32(order.validOutputs[i].decimals), + ethereum.Value.fromUnsignedBigInt(order.validOutputs[i].vaultId), + ]; + + tupleValidOutputs.push(changetype(VO)); + } + + let tupleArray: Array = [ + ethereum.Value.fromAddress(order.owner), + ethereum.Value.fromBoolean(order.handleIO), + ethereum.Value.fromTuple(evaluable), + ethereum.Value.fromTupleArray(tupleValidInputs), + ethereum.Value.fromTupleArray(tupleValidOutputs), + ]; + + let tuple = changetype(tupleArray); + let encodedOrder = ethereum.encode(ethereum.Value.fromTuple(tuple))!; + let keccak256 = crypto.keccak256(encodedOrder); + let orderHashHex = getEvenHex(keccak256.toHex()); + + let order_ = Order.load(orderHashHex); + if (order_) return order_; + else return new Order(orderHashHex); +} + +function hexToBI(hexString: string): BigInt { + return BigInt.fromUnsignedBytes( + changetype(Bytes.fromHexString(hexString).reverse()) + ); +} + +export function createTakeOrderConfig(txHash: string): TakeOrderEntity { + for (let i = 0; ; i++) { + let orderClear = TakeOrderEntity.load(`${txHash}-${i}`); + if (!orderClear) { + return new TakeOrderEntity(`${txHash}-${i}`); + } + } + return new TakeOrderEntity(""); +} + +export function createOrderClear(txHash: string): OrderClear { + for (let i = 0; ; i++) { + let orderClear = OrderClear.load(`${txHash}-${i}`); + if (!orderClear) { + return new OrderClear(`${txHash}-${i}`); + } + } + return new OrderClear(""); +} + +export function createVaultDeposit(txHash: string): VaultDeposit { + for (let i = 0; ; i++) { + let orderClear = VaultDeposit.load(`${txHash}-${i}`); + if (!orderClear) { + return new VaultDeposit(`${txHash}-${i}`); + } + } + return new VaultDeposit(""); +} + +export function createVaultWithdraw(txHash: string): VaultWithdraw { + for (let i = 0; ; i++) { + let orderClear = VaultWithdraw.load(`${txHash}-${i}`); + if (!orderClear) { + return new VaultWithdraw(`${txHash}-${i}`); + } + } + return new VaultWithdraw(""); +} + +export function getOB(obAddress_: Address): OrderBook { + let orderBook = OrderBook.load(obAddress_); + if (!orderBook) { + orderBook = new OrderBook(obAddress_); + orderBook.address = obAddress_; + orderBook.save(); + } + return orderBook; +} + +export function getRainMetaV1(meta_: Bytes): RainMetaV1 { + const metaV1_ID = getKeccak256FromBytes(meta_); + + let metaV1 = RainMetaV1.load(metaV1_ID); + + if (!metaV1) { + metaV1 = new RainMetaV1(metaV1_ID); + metaV1.metaBytes = meta_; + metaV1.content = []; + metaV1.save(); + } + + return metaV1; +} + +export function getKeccak256FromBytes(data_: Bytes): Bytes { + return Bytes.fromByteArray(crypto.keccak256(Bytes.fromByteArray(data_))); +} + +export function isHexadecimalString(str: string): boolean { + // Check if string is empty + if (str.length == 0) { + return false; + } + + // Check if each character is a valid hexadecimal character + for (let i = 0; i < str.length; i++) { + let charCode = str.charCodeAt(i); + if ( + !( + (charCode >= 48 && charCode <= 57) || // 0-9 + (charCode >= 65 && charCode <= 70) || // A-F + (charCode >= 97 && charCode <= 102) + ) + ) { + // a-f + return false; + } + } + + return true; +} + +export function createTransaction( + hash: string, + block: ethereum.Block +): Transaction { + let transaction = Transaction.load(hash); + if (!transaction) { + transaction = new Transaction(hash); + transaction.blockNumber = block.number; + transaction.timestamp = block.timestamp; + transaction.save(); + } + return transaction; +} + +export function toDisplay(amount: BigInt, token: string): BigDecimal { + let erc20 = createToken(Address.fromString(token)); + if (erc20) { + let denominator = BigInt.fromString(getZeros(erc20.decimals)); + return amount.toBigDecimal().div(denominator.toBigDecimal()); + } + return amount.toBigDecimal().div(BigDecimal.fromString(getZeros(0))); +} + +export function toDisplayWithDecimals( + amount: BigInt, + decimals: i32 +): BigDecimal { + let denominator = BigInt.fromString(getZeros(decimals)); + return amount.toBigDecimal().div(denominator.toBigDecimal()); +} + +function getZeros(num: number): string { + let s = "1"; + for (let i = 0; i < num; i++) { + s = s + "0"; + } + return s; +} + +export function gcd(a: BigInt, b: BigInt): BigInt { + if (b.equals(BigInt.zero())) { + return a; + } else { + return gcd(b, a.mod(b)); + } +} + +export function BDtoBIMultiplier(n1: BigDecimal, n2: BigDecimal): BigInt { + let n1_split = n1.toString().split("."); + let n1_decimals = n1_split.length == 1 ? 0 : n1_split[1].length; + + let n2_split = n2.toString().split("."); + let n2_decimals = n2_split.length == 1 ? 0 : n2_split[1].length; + + let number: BigDecimal; + if (n1_decimals > n2_decimals) { + number = n1; + } else { + number = n2; + } + let location = number.toString().indexOf("."); + let len = number.toString().slice(location + 1).length; + return BigInt.fromString(getZeros(len)); +} + +export function createSignedContext(txHash: string): SignedContext { + for (let i = 0; ; i++) { + let signedContext = SignedContext.load(`${txHash}-${i}`); + if (!signedContext) { + return new SignedContext(`${txHash}-${i}`); + } + } + return new SignedContext(""); +} +export function createContextEntity(txHash: string): ContextEntity { + for (let i = 0; ; i++) { + let contextEntity = ContextEntity.load(`${txHash}-${i}`); + if (!contextEntity) { + return new ContextEntity(`${txHash}-${i}`); + } + } + return new ContextEntity(""); +}