This site is in maintenance mode. Features may be unstable.
Warning! On-chain actions are not disabled.
Patract Hub's treasury report for Redspot v0.3 (contract dev scaffold)
5 weeks ago, [Patract Hub](https://patract.io) applied a [Treasury Proposal #23](https://polkadot.polkassembly.io/treasury/23) for Redspot v0.3, and now we have finished all the development works on time. For a quick review, you can visit our website at https://redspot.patract.io/. Redspot v0.1 has the basic functions of compiling contracts, testing contracts and deploying contracts. The v0.2 version refactored the bottom layer, allowing Redspot to have better scalability, adding TypeScript support, a flexible plug-in system, and simpler and more intuitive logic. Redspot v0.2 already has a very complete basic framework, so v0.3 mainly upgrades the plug-in system on the basis of v0.2, adds chai matcher, sample plug-ins, and supplements Redspot's official website and documentation tutorials. ## Summary of Redspot’s Future Plan: - v0.1: Build core functions based on Truffle framework - v0.2: Migrate to Hardhat framework to enhance the extensibility of plugins and add some features to provide a smoother development workflow - v0.3: Promote Redspot and allow more contract developers to participate. Combine the features of Substrate to add various plug-ins, such as Waffle, Jupiter, Gas report. - v0.4: Provide Typescript type support for contracts (similar to Typechain), integrate multi-language SDK, etc. ## New features in Redspot v0.3 Now let us show the design and implementation of v0.3, and how to run and verify it. Like v0.2, you can install a template project locally via `npx redspot-new erc20`. For specific usage methods, please refer to [Treasury Report #156](https://polkadot.polkassembly.io/post/156) of v0.2 ### TypeScript upgrade The templates provided by Redspot by default are based on TypeScript, so developers who are not familiar with TypeScript can easily use TypeScript's code hints and type system. In v0.2, if a new plug-in is introduced, and this plug-in extends the type of Redspot Runtime Environment, we need to manually import the type file of the plug-in in the tsconfig.json file so that TypeScript can recognize it correctly. like this: ```javascript "files": [ "./redspot.config.ts" "./node_modules/@redspot/patract/type-extensions.d.ts" ] ``` We improved this in v0.3. Now the plug-in type file does not need to be set up specially, the type will be automatically imported. Now the types of Redspot are all provided by `redspot/types`, and the plug-in can describe the types of new attributes and interfaces by inheriting RuntimeEnvironment. like this: ```javascript // type-extensions.ts import 'redspot/types'; declare module 'redspot/types' { interface RuntimeEnvironment { [pluginName]: { ... // type }; } } ``` Then import this file `import "type-extensions.ts"` inside the `index.js` of the plugin. In this way, when a plug-in is imported, TypeScript will automatically recognize the type file. We always recommend users to use TypeScript. Redspot uses `ts-node` to run TypeScript code internally. When running scripts or tests, if the value of TS_NODE_TRANSPILE_ONLY is not explicitly set, Redspot will defaultly set TS_NODE_TRANSPILE_ONLY to true. This can speed up the compilation speed of ts-node and avoid type errors. In Redspot, you can write TypeScript in the same way as JavaScript. Users will not have additional learning burden, but they can directly enjoy the benefits of code hints brought by TypeScript. ### Optimization of plug-in system In v0.2 we need to use `usePlugin` to introduce a plugin. In v0.3, the introduction of plugins has become more natural. Only the plugins you need in `require` or `import` in `redspot.config.ts`. ```javascript import '@redspot/patract' // or require('@redspot/patract') ``` Now the plug-in does not need to export a function, but directly calls extendEnvironment to extend the Runtime Environment: ```javascript import { extendEnvironment } from 'redspot/config'; extendEnvironment((env) => { env.patract = ... }); ``` We have abandoned The plug-in `internalTask` and changed to `subtask`. `subtask` supports complex types and no longer needs `stringified`. ### Add artifacts object In v0.2, you need to import the module `redspot/plugins` to get the `readAbi` and `readWasm` functions: ```javascript const { readAbi, readWasm } = require("redspot/plugins") readAbi(env.paths.artifacts, contractName) // get abi readWasm(env.paths.artifacts, contractName) // get wasm ``` Now the Redspot Runtime Environment will export an artifacts object, which contains artifact-related methods. ```javascript const { artifacts } = env artifacts.readAbi(contractName) // get abi artifacts.readWasm(contractName) // get wasm artifacts.saveArtifact(path) // save artifact ``` get the abi file at the same time: ```javascript const { artifacts } = env artifacts.readAbiSync(contractName) // get abi artifacts.readWasmSync(contractName) // get wasm ``` ### Refactor the network object In v0.2, `env.network` will import a `provider` object compatible with `Polkadot.js`'s `ProviderInterface` type. Now we have extended the network object and added more methods and properties. ```javascript export interface Network { name: string; config: NetworkConfig; provider: WsProvider; api: ApiPromise; registry: Registry; keyring: Keyring; getSigners(): Promise; createSigner(pair: KeyringPair): Signer; gasLimit?: BN; explorerUrl?: string; utils: { encodeSalt( salt?: Uint8Array | string | null, signer?: Signer ): Promise; }; } ``` Now we are more compatible with `polkadot.js`. `api`, `registry`, `keyring` and `provider` are all from `polkadot.js`, Redspot will automatically handle the initialization and setting of these objects. The `signer` object returned by `getSigners` is compatible with the `signer` in `polkadot.js` and can be directly used for transaction signing. If you want to create a `signer`, it is also very simple by calling the createSigner method: ```javascript const uri = 'bottom drive obey lake curtain smoke basket hold race lonely fit walk//Alice'; const signer = createSigner(network.keyring.createFromUri(uri)); ``` ### Add @redspot/chai plug-in This plug-in is used to simplify the writing of test cases. The values returned by all APIs in `polkadot.js` are not basic types, so it is impossible to simply judge whether they are equal. Many times you need to write: `expect(account.toString()).to.equal('5GHs.....')`. If you write like this: `expect(account.eq('5GHs....')).to.true`, you will lose the detailed error message provided by the test framework. `@redspot/chai` solves this problem, it covers the `eq` and `equal` matchers of `chai` internally. example: ```javascript expect(Uint8Array.from([0x11,0x22])).to.equal('0x1122'); // true expect(Uint8Array.from([0x11,0x22])).to.equal('0x2122'); // false expect(new BN('10000000000000000')).to.equal(10000000000000000n); // true expect(new BN('10000000000000000')).to.equal('10000000000000000'); // true expect(AccountId).to.equal('5GHs....'); // true ``` For contracts, if you use the `@redspot/patract` plugin, you can easily perform contract event matching, simple and easy to read: ```javascript // Detects if a transfer event is sent for the transaction await expect(contract.tx.transfer(receiver.address, 7)) .to.emit(contract, 'Transfer') // Detects if the transaction sends a transfer event with the specified parameters await expect(contract.tx.transfer(receiver.address, 7)) .to.emit(contract, 'Transfer') .withArgs(sender.address, receiver.address, 7); ``` You can also detect the change in the balance of the `erc20` contract: ```javascript await expect(() => contract.tx.transfer(receiver.address, 7) ).to.changeTokenBalance(contract, receiver, 7); await expect(() => contract.tx.transfer(receiver.address, 7) ).to.changeTokenBalances(contract, [contract.signer, receiver], [-7, 7]); ``` There are also some tool adapters: ```javascript expect(...).to.properAddress expect(...).to.properHex ``` If you want to import the `@redspot/chai` plugin, you only need to add `import "@redspot/chai"` in `redspot.config.ts`. When the plugin is imported, it will automatically add the matcher to chai. Now the complete test case of `erc20` is as follows: ```javascript import BN from 'bn.js'; import { expect } from 'chai'; import { patract, network, artifacts } from 'redspot'; const { getContractFactory, getRandomSigner } = patract; const { api, getSigners } = network; describe('ERC20', () => { after(() => { return api.disconnect(); }); async function setup() { const one = new BN(10).pow(new BN(api.registry.chainDecimals)); const signers = await getSigners(); const Alice = signers[0]; const sender = await getRandomSigner(Alice, one.muln(100)); const contractFactory = await getContractFactory('erc20', sender); const contract = await contractFactory.deploy('new', '1000'); const abi = artifacts.readAbi('erc20'); const receiver = await getRandomSigner(); return { sender, contractFactory, contract, abi, receiver, Alice, one }; } it('Assigns initial balance', async () => { const { contract, sender } = await setup(); sender.pair.publicKey const result = await contract.query.balanceOf(sender.address); expect(result.output).to.equal(1000); }); it('Assigns initial balance', async () => { const { contract, sender } = await setup(); const result = await contract.query.balanceOf(sender.address); expect(result.output).to.equal(1000); }); it('Transfer adds amount to destination account', async () => { const { contract, receiver } = await setup(); await expect(() => contract.tx.transfer(receiver.address, 7) ).to.changeTokenBalance(contract, receiver, 7); await expect(() => contract.tx.transfer(receiver.address, 7) ).to.changeTokenBalances(contract, [contract.signer, receiver], [-7, 7]); }); it('Transfer emits event', async () => { const { contract, sender, receiver } = await setup(); await expect(contract.tx.transfer(receiver.address, 7)) .to.emit(contract, 'Transfer') .withArgs(sender.address, receiver.address, 7); }); it('Can not transfer above the amount', async () => { const { contract, receiver } = await setup(); await expect(contract.tx.transfer(receiver.address, 1007)).to.not.emit( contract, 'Transfer' ); }); it('Can not transfer from empty account', async () => { const { contract, Alice, one, sender } = await setup(); const emptyAccount = await getRandomSigner(Alice, one.muln(10)); await expect( contract.tx.transfer(sender.address, 7, { signer: emptyAccount }) ).to.not.emit(contract, 'Transfer'); }); }); ``` ### Add @redspot/gas-reporter plug-in `@redspot/gas-reporter` will print the gas consumption at the end of each test. The plugin covers the Test task. When calling test, it adds a reporter to mocha. ```javascript subtask(TASK_TEST_RUN_MOCHA_TESTS).setAction( async (args: any, rse, runSuper) => { const options = getOptions(rse); if (options.enabled) { mochaConfig = rse.config.mocha || {}; mochaConfig.reporter = GasReporter; mochaConfig.reporterOptions = options; rse.config.mocha = mochaConfig; } return runSuper(); } ); ``` Input `npx redspot test`,and will get results:  The gas-reporter will monitor the block at the beginning of the test, and process the data of all blocks at the end of the test, and get the contract transaction. Then use the transaction data of the contract to call `contract.call` to get the estimated gas consumption value. Due to the estimated gas value, it is not very accurate. So gas-reporter will also analyze the events in the block to get the weight value of the contract transaction. Both gas and weight will be displayed after the test case is completed. ### Add @redspot/jupiter plug-in This is a sample plug-in that shows how Redspot adapts some chains. This plugin modifies the contract address calculation method, changing the random salt to account nonce. And it only takes effect for the jupiter chain. ```javascript extendEnvironment((env) => { env.network.utils.encodeSalt = async function encodeSalt( salt?: string | Uint8Array | null, signer?: Signer ): Promise { if (!signer) throw new Error('Need Signer'); const accountInfo = await signer.api.query.system.account(signer.address); const runtimeVersion = signer.api.runtimeVersion; const isJupiter = runtimeVersion.specName .toString() .toLowerCase() .includes('jupiter'); if (!isJupiter) return env.network.utils.encodeSalt(salt, signer); const nonce = accountInfo.nonce.toNumber(); return salt instanceof Bytes ? salt : compactAddLength(numberToU8a(nonce)); }; }); ``` We get the `specname` through the `runtimeversion` of the chain. Then judge whether the `specname` is Jupiter, and if not, return to the original `encodeSalt` method. Our subsequent judgment process is integrated into the Redspot framework. If you need to add unique types to the current chain, you can do this in the plugin: ```javascript extendEnvironment((rse) => { rse.network.api.registry.setKnownTypes({ types: { Address: 'AccountId', LookupSource: 'AccountId' } }) }); ``` In this way, users do not need to manually specify types in `redspot.config.ts`. Only need to introduce the plug-in of the chain. We will add more chain plugins in the future, such as `europa`. It has some special RPCs to facilitate contract testing. Such as the function of resetting the state of the block. ### Redspot's official website and tutorials We designed and developed the Redspot homepage, filled with some documents and tutorials. You can view it here [https://redspot.patract.io\](https://redspot.patract.io/) Our homepage:  Our Toturial page:  Our Documentation page:  ### Other optimizations * We have adjusted the Redspot release process, and Redspot will now be released automatically through CI. Now, like polkadot.js, x.y.1 will be the stable version of Redspot, and x.y.2-z will be the test version of Redspot. We also added the vers parameter to Redspot-template. If you want to install a historical version of Redspot, you can do by this: ```bash npx redspot-new erc20 --vers ^0.4.2-15 ``` * We have sorted out the types of Redspot's framework, and now all types can be found in `redspot/types`. * We have moved some exported functions and properties of `@redspot/patract` to network to facilitate other plugins to extend. ## Recap of v0.3 verification - M1: Homepage and API docs page development > View Redspot's homepage [https://redspot.patract.io\](https://redspot.patract.io/) - M2: Write detailed tutorials and documents > View Redspot's tutorials https://redspot.patract.io/en/tutorial/ and https://redspot.patract.io/en/plugins/ . > Create a new Redspot project and view the typescript code hints in the project. - M3: Upgrade Redspot's plugin system > Optimizations and upgrades of redspot have been demonstrated above. - M4: Develop Jupiter plugin and Gas Report v0.2 plugin > The gas-reports plug-in has been added to the default template. Just run `npx redspot test` to get the gas-reporter results. > You can use the `@redspot/jupiter` plugin by importing the jupiter plugin `import ‘@redspot/jupiter’`. If you are accessing the jupiter network, the contract address generated at this time is calculated through account nonce. - M5: Develop assertion library > Check the test case for sample erc20 template ## For v0.4 and the future By v0.3, most of the functions of the design target has finished and Redspot has been in production level. Developers can use Redspot as a powerful tool to facilitate their development for WASM contracts. We will need more time to build Patract Hub's other tools and services, like Himalia, Leda and Carpo, and integrate them with Redspot v0.4 or future versions, so stay tuned.
Comments