From 36684d3d89b44873bd391987bd37aa9b8f7404ab Mon Sep 17 00:00:00 2001 From: ppe Date: Mon, 12 Sep 2022 18:10:40 +0200 Subject: [PATCH] docs: bundled contract format description --- docs/BUNDLED_CONTRACT.md | 181 +++++++++++++++++++++++++++++++++++++++ tools/deploytest.ts | 26 +++--- 2 files changed, 194 insertions(+), 13 deletions(-) create mode 100644 docs/BUNDLED_CONTRACT.md diff --git a/docs/BUNDLED_CONTRACT.md b/docs/BUNDLED_CONTRACT.md new file mode 100644 index 0000000..a53d584 --- /dev/null +++ b/docs/BUNDLED_CONTRACT.md @@ -0,0 +1,181 @@ +# Warp Bundled Contract Format + +This document describes the core concepts behind the Warp bundled contract format. + +## Introduction + +The idea behind Warp Bundled Contract Format is to increase the Developer and User Experience. +Normally, when a contract is being deployed on Arweave, one have to wait some time: + +1. for the contract's transactions mining (~2 minutes) +2. for the proper contract's transactions confirmation (assuming at least 10 blocks - ~20 minutes) + +This in total gives ~20-25 minutes, which: + +1. Breaks the DX, e.g. in case developer wants to quickly test the new contract version on Arweave mainnet +2. Breaks the UX, e.g. if given protocol deploys the contracts dynamically - e.g. via GUI (e.g. deploying + NFT collections, connecting deployed data with access rights, etc.) + +Additionally, deploying contracts requires some amount of ARs in the wallet - which might further increase +the entry barrier, both for developers and given protocol users. + +## Advantages of using Warp Gateway for contracts deployment +1. Contract is instantly available - as soon as proper response from Bundlr network is received. +2. Contract deployment does not require any ARs in the wallet - the deployment via Bundlr network is either +fully subsidized by the Arweave (for transactions <= 100KiB) or by the Warp (for transactions > 100KiB). +The max transaction size is currently 2MiB. +3. Even though the Bundlr transactions are created and signed by the Warp's wallet, it is still possible to identify +the original transaction owner/signer. + +## How it works + +Instead of posting the contract and contract source transactions directly to Arweave mainnet, both are sent to Warp +Gateway (`/gateway/contracts/deploy` endpoint) (this is the default behaviour of Warp's SDK `warp.createContract.deploy` function, when `forMainnet` instance is being used). + +The Warp Gateway then: + +1. Posts contract transactions (i.e. the base contract transaction and contract source transaction - or only the + contract interaction, if deploying from existing source) to the Bundlr network. Each contract transaction is sent as + a separate Bundlr transaction - as the `data` of the bundled transaction. The Bundlr transaction in this case might + be considered as a "carrier" of the original transaction. Additionally - some additional tags to the Bundlr transaction + are added. + +```ts +const bTx = bundlr.createTransaction(JSON.stringify(transaction), {tags}); +await bTx.sign(); +const bundlrResponse = await bTx.upload(); +``` + +Transaction which is sent to Bundlr, consists of: + +| Transaction field | Value | +|---------------------------------------------|-------------------------------------------------------| +| `data` | The original transaction, JSON stringified | +| `tag['Uploader']` | `RedStone` | +| `tag['Uploader-Contract-Owner']` | The original owner/signar of the contract transaction | +| `tag['Uploader-Tx-Id']` | The id of the original transaction | +| ...all the tags of the original transaction | | + +**NOTE** The original transaction is not modified in any way - this is to preserve the original +signature! + +2. After receiving proper response and recipes from Bundlr, the Warp gateway indexes the contract + transactions data internally - to make them instantly available. + +3. Finally, the Warp gateway returns an object as a `response` - that consists of fields: +- `response.contractId` - the original contract tx id +- `response.bundleContractId` - the Bundlr contract tx id +- `response.srcTxId` - the original contract source transaction id +- `response.bundleSrcId` - the Bundlr source tx id. + +## Contract transaction retrieval via Arweave gateway + +1. Directly via `response.bundleContractId` - e.g. https://arweave.net/Yy9WoplIYqy03O7_ovUr4TrvwryxHEneuNep7ATDftI + **NOTE 1** The response object contains the full, original tx - including its data and id + - `hs9JlOG0LMkTa4VdJ-nf6I46kLbWbCpvZczdl3N-ASQ` in this case. + **NOTE 2** The `data` field contains the original contract's data. Usually it is an initial contract state or an asset - for AtomicNFT contracts. + **NOTE 3** The `Yy9WoplIYqy03O7_ovUr4TrvwryxHEneuNep7ATDftI` is the Bundlr's tx id - assigned by + the `bundlr.createTransaction()`. + It is part of `ANS-104` bundle, that is uploaded to Arweave by Bundlr network - with the original tx as a `data-item`. + +2. Using the GQL endpoint, using the original contract tx id (`hs9JlOG0LMkTa4VdJ-nf6I46kLbWbCpvZczdl3N-ASQ` in this case) and `Uploader-Tx-Id` tag, e.g. + +```qql +query { + transactions( + tags: [{ + name: "Uploader-Tx-Id", + values: ["hs9JlOG0LMkTa4VdJ-nf6I46kLbWbCpvZczdl3N-ASQ"] + }] + ) { + edges { + node { + id + tags { + name + value + } + block { + height + } + } + } + } +} +``` + +Example Response: + +```json +{ + "transactions": { + "edges": [ + { + "node": { + "id": "Yy9WoplIYqy03O7_ovUr4TrvwryxHEneuNep7ATDftI", + "tags": [ + { + "name": "Uploader", + "value": "RedStone" + }, + { + "name": "Uploader-Contract-Owner", + "value": "33F0QHcb22W7LwWR1iRC8Az1ntZG09XQ03YWuw2ABqA" + }, + { + "name": "Uploader-Tx-Id", + "value": "hs9JlOG0LMkTa4VdJ-nf6I46kLbWbCpvZczdl3N-ASQ" + }, + { + "name": "Uploader-Bundler", + "value": "https://node2.bundlr.network" + }, + { + "name": "App-Name", + "value": "SmartWeaveContract" + }, + { + "name": "App-Version", + "value": "0.3.0" + }, + { + "name": "Contract-Src", + "value": "cK9mwwuR2CR72ubcI34ClssjQUL5G7cZEd649CVPJWw" + }, + { + "name": "SDK", + "value": "RedStone" + }, + { + "name": "Content-Type", + "value": "application/json" + } + ], + "block": { + "height": 1013105 + } + } + } + ] + } +} +``` + +**NOTE** The `transactions.edges.node.id` is an id of the Bundlr transaction - the 'carrier' of the original transaction. + +## Contract transaction retrieval via Warp gateway +The Warp `/gateway/contract` endpoint allows to retrieve the Bundle contracts data directly via the original tx id. +This endpoint is used by default for loading contracts data by the Warp SDK - when `forMainnet` instance is being used. + + +## Contract transaction data retrieval +1. Using the Warp gateway - `gateway/contract-data/:original-tx-id`. +E.g.: https://gateway.redstone.finance/gateway/contract-data/hs9JlOG0LMkTa4VdJ-nf6I46kLbWbCpvZczdl3N-ASQ. +This endpoint underneath maps the original tx id to the Bundlr tx id. +Having the original Bundlr tx id - it loads the original tx data either from arweave.net cache - or fallbacks to the Bundlr node. +The data is then decoded using a dedicated function - https://github.com/warp-contracts/gateway/blob/main/src/gateway/router/routes/contractDataRoute.ts#L57 +2. Using Arweave gateway - + 1. get the Bundlr tx id - as shown in the GQL example + 2. load the tx data using the `arweave.net/{txId}` endpoint + 3. decode the response `data` field using the https://github.com/warp-contracts/gateway/blob/main/src/gateway/router/routes/contractDataRoute.ts#L57 + algorithm. diff --git a/tools/deploytest.ts b/tools/deploytest.ts index 3c2fc20..bae6830 100644 --- a/tools/deploytest.ts +++ b/tools/deploytest.ts @@ -28,10 +28,10 @@ async function main() { address: "http://13.53.39.138:5666" }) .build()*/ - const contract = warp.contract("f4skRMstoodrRluvl4OCY-Xo50AamgxYwBCZKzw3Uvo"); + //const contract = warp.contract("qx1z1YInqcp4Vf5amJER2R8E_SEyY6pmHS1912VSUAs"); - /*const jsContractSrc = fs.readFileSync(path.join(__dirname, 'data/js/token-pst.js'), 'utf8'); + const jsContractSrc = fs.readFileSync(path.join(__dirname, 'data/js/token-pst.js'), 'utf8'); const wasmContractSrc = fs.readFileSync(path.join(__dirname, 'data/rust/rust-pst_bg.wasm')); const initialState = fs.readFileSync(path.join(__dirname, 'data/js/token-pst.json'), 'utf8'); @@ -40,35 +40,35 @@ async function main() { wallet, initState: initialState, src: jsContractSrc, - });*/ + }); // case 2 - deploy from source, js contract - /*const contractTxId = await warp.createContract.deployFromSourceTx({ + /*const {contractTxId} = await warp.createContract.deployFromSourceTx({ wallet, initState: initialState, srcTxId: "Hj0S0iK5rG8yVf_5u-usb9vRZg1ZFkylQLXu6rcDt-0", - }, true);*/ + });*/ // case 3 - full deploy, wasm contract - /*const contractTxId = await warp.createContract.deploy({ + /*const {contractTxId} = await warp.createContract.deploy({ wallet, initState: initialState, src: wasmContractSrc, wasmSrcCodeDir: path.join(__dirname, 'data/rust/src'), wasmGlueCode: path.join(__dirname, 'data/rust/rust-pst.js') - }, true);*/ + });*/ // case 4 - deploy from source, wasm contract - /*const contractTxId = await warp.createContract.deployFromSourceTx({ + /*const {contractTxId} = await warp.createContract.deployFromSourceTx({ wallet, initState: initialState, srcTxId: "5wXT-A0iugP9pWEyw-iTbB0plZ_AbmvlNKyBfGS3AUY", - }, true);*/ + });*/ - /*const contract = warp.contract(contractTxId) - .setEvaluationOptions({ + const contract = warp.contract(contractTxId) + /*.setEvaluationOptions({ bundlerUrl: "http://13.53.39.138:5666/" - }) + })*/ .connect(wallet); await contract.writeInteraction({ @@ -84,7 +84,7 @@ async function main() { await contract.writeInteraction({ function: "storeBalance", target: "M-mpNeJbg9h7mZ-uHaNsa5jwFFRAq0PsTkNWXJ-ojwI", - });*/ + }); const {cachedValue} = await contract.readState();