diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd338f46d..9f6725f9f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -69,6 +69,25 @@ jobs: run: | SKIP_WASM_BUILD=1 cargo clippy --all-targets --workspace --features runtime-benchmarks --features try-runtime -- -D warnings + lint-e2e: + runs-on: + group: laos + labels: ubuntu-16-cores + steps: + - uses: actions/checkout@v3 + - name: Use Node.js 18 + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: | + npm install + working-directory: ./e2e-tests + - name: Run lint + run: | + npm run fmt-check + working-directory: ./e2e-tests + test: runs-on: group: laos diff --git a/Cargo.lock b/Cargo.lock index 8d0159f04..94d9905ef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5114,6 +5114,7 @@ dependencies = [ "pallet-message-queue", "pallet-multisig", "pallet-parachain-staking", + "pallet-precompiles-benchmark", "pallet-proxy", "pallet-session", "pallet-sudo", @@ -7433,6 +7434,30 @@ dependencies = [ "test-utils", ] +[[package]] +name = "pallet-precompiles-benchmark" +version = "0.1.0" +dependencies = [ + "fp-evm", + "frame-benchmarking", + "frame-support", + "frame-system", + "hex", + "laos-primitives", + "pallet-balances", + "pallet-evm", + "pallet-timestamp", + "pallet-vesting", + "parity-scale-codec", + "precompile-utils", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "test-utils", +] + [[package]] name = "pallet-preimage" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index ccc65fea6..0fa861765 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -252,6 +252,7 @@ substrate-fixed = { git = "https://github.com/encointer/substrate-fixed", defaul pallet-laos-evolution = { path = "./pallets/laos-evolution", default-features = false } pallet-asset-metadata-extender = { path = "./pallets/asset-metadata-extender", default-features = false } pallet-parachain-staking = { path = "./pallets/parachain-staking", default-features = false } +pallet-precompiles-benchmark = { path = "./pallets/precompiles-benchmark", default-features = false} # Primitives laos-primitives = { path = "./primitives", default-features = false } diff --git a/e2e-tests/compile_contracts.sh b/e2e-tests/compile_contracts.sh new file mode 100755 index 000000000..ca98eef31 --- /dev/null +++ b/e2e-tests/compile_contracts.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# we need to move .sol files under /contracts folder so that truffle works properly +cp -r ../pallets/laos-evolution/src/precompiles/evolution_collection_factory/contracts contracts +cp -r ../pallets/laos-evolution/src/precompiles/evolution_collection/contracts contracts +cp -r ../pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/contracts contracts +cp -r ../pallets/benchmark/src/precompiles/vesting/contracts contracts + +truffle compile +rm -rf contracts diff --git a/e2e-tests/package.json b/e2e-tests/package.json index 5ca876b8c..89caa2fda 100644 --- a/e2e-tests/package.json +++ b/e2e-tests/package.json @@ -6,7 +6,7 @@ "scripts": { "fmt-check": "prettier ./tests --check", "fmt": "prettier ./tests --write", - "build": "cp -r ../pallets/laos-evolution/src/precompiles/evolution_collection_factory/contracts contracts && cp -r ../pallets/laos-evolution/src/precompiles/evolution_collection/contracts contracts && cp -r ../pallets/asset-metadata-extender/src/precompiles/asset_metadata_extender/contracts contracts && truffle compile && rm -rf contracts", + "build": "./compile_contracts.sh", "test": "mocha -r ts-node/register -t 270000 'tests/**/*.ts'", "test-sql": "FRONTIER_BACKEND_TYPE='sql' mocha -r ts-node/register 'tests/**/*.ts'" }, diff --git a/e2e-tests/tests/config.ts b/e2e-tests/tests/config.ts index fc5945b88..c6a116328 100644 --- a/e2e-tests/tests/config.ts +++ b/e2e-tests/tests/config.ts @@ -3,6 +3,7 @@ import { AbiItem } from "web3-utils"; import AssetMetadataExtender from "../build/contracts/AssetMetadataExtender.json"; import EvolutionCollection from "../build/contracts/EvolutionCollection.json"; import EvolutionCollectionFactory from "../build/contracts/EvolutionCollectionFactory.json"; +import Vesting from "../build/contracts/Vesting.json"; // Node config export const RUNTIME_SPEC_NAME = "laos"; @@ -21,18 +22,16 @@ export const ETH_BLOCK_GAS_LIMIT = 15000000; // The same configuration as runtim export const GAS_LIMIT = ETH_BLOCK_GAS_LIMIT - 10000000; // LAOS Evolution Contract -export const EVOLUTION_COLLETION_FACTORY_ABI = EvolutionCollectionFactory.abi as AbiItem[]; +export const EVOLUTION_COLLECTION_FACTORY_ABI = EvolutionCollectionFactory.abi as AbiItem[]; export const EVOLUTION_COLLECTION_ABI = EvolutionCollection.abi as AbiItem[]; -export const CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000403"; +export const EVOLUTION_COLLECTION_FACTORY_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000403"; export const SELECTOR_LOG_NEW_COLLECTION = "0x5b84d9550adb7000df7bee717735ecd3af48ea3f66c6886d52e8227548fb228c"; export const SELECTOR_LOG_MINTED_WITH_EXTERNAL_TOKEN_URI = "0xa7135052b348b0b4e9943bae82d8ef1c5ac225e594ef4271d12f0744cfc98348"; export const SELECTOR_LOG_EVOLVED_WITH_EXTERNAL_TOKEN_URI = "0xdde18ad2fe10c12a694de65b920c02b851c382cf63115967ea6f7098902fa1c8"; -export const SELECTOR_LOG_OWNERSHIP_TRANSFERRED = - "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0"; -export const SELECTOR_LOG_PUBLIC_MINTING_ENABLED = - "0x8ff3deee4c40ab085dd8d7d0c848cb5295e4ab5faa32e5b60e3936cf1bdc77bf"; +export const SELECTOR_LOG_OWNERSHIP_TRANSFERRED = "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0"; +export const SELECTOR_LOG_PUBLIC_MINTING_ENABLED = "0x8ff3deee4c40ab085dd8d7d0c848cb5295e4ab5faa32e5b60e3936cf1bdc77bf"; export const SELECTOR_LOG_PUBLIC_MINTING_DISABLED = "0xebe230014056e5cb4ca6d8e534189bf5bfb0759489f16170654dce7c014b6699"; @@ -44,6 +43,10 @@ export const SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI = export const SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI = "0xe7ebe38355126fe0c3eab0ec03eb1b94ff501458a80713c9eb8b737334a651ff"; +// Vesting contract +export const VESTING_CONTRACT_ADDRESS = "0x0000000000000000000000000000000000000406"; +export const VESTING_ABI = Vesting.abi as AbiItem[]; + // Constants export const MAX_U96 = new BN("79228162514264337593543950336"); // 2^96 - 1 export const REVERT_BYTECODE = "0x60006000fd"; diff --git a/e2e-tests/tests/test-create-collection.ts b/e2e-tests/tests/test-create-collection.ts index bc7e854fa..cf54f9c66 100644 --- a/e2e-tests/tests/test-create-collection.ts +++ b/e2e-tests/tests/test-create-collection.ts @@ -2,8 +2,8 @@ import { expect } from "chai"; import { step } from "mocha-steps"; import Contract from "web3-eth-contract"; import { - CONTRACT_ADDRESS, - EVOLUTION_COLLETION_FACTORY_ABI, + EVOLUTION_COLLECTION_FACTORY_CONTRACT_ADDRESS, + EVOLUTION_COLLECTION_FACTORY_ABI, GAS_LIMIT, GAS_PRICE, FAITH, @@ -21,18 +21,22 @@ describeWithExistingNode("Frontier RPC (Create Collection)", (context) => { let testCollectionAddress: string; before(async function () { - contract = new context.web3.eth.Contract(EVOLUTION_COLLETION_FACTORY_ABI, CONTRACT_ADDRESS, { - from: FAITH, - gasPrice: GAS_PRICE, - gas: GAS_LIMIT, - }); + contract = new context.web3.eth.Contract( + EVOLUTION_COLLECTION_FACTORY_ABI, + EVOLUTION_COLLECTION_FACTORY_CONTRACT_ADDRESS, + { + from: FAITH, + gasPrice: GAS_PRICE, + gas: GAS_LIMIT, + } + ); context.web3.eth.accounts.wallet.add(FAITH_PRIVATE_KEY); }); step("when collection is created, it should return owner", async function () { const collectionContract = await createCollection(context); testCollectionContract = collectionContract; - + const owner = await collectionContract.methods.owner().call(); expect(owner).to.be.eq(FAITH); }); @@ -55,9 +59,7 @@ describeWithExistingNode("Frontier RPC (Create Collection)", (context) => { // event topics expect(result.events.NewCollection.raw.topics.length).to.be.eq(2); expect(result.events.NewCollection.raw.topics[0]).to.be.eq(SELECTOR_LOG_NEW_COLLECTION); - expect(result.events.NewCollection.raw.topics[1]).to.be.eq( - context.web3.utils.padLeft(FAITH.toLowerCase(), 64) - ); + expect(result.events.NewCollection.raw.topics[1]).to.be.eq(context.web3.utils.padLeft(FAITH.toLowerCase(), 64)); // event data expect(result.events.NewCollection.raw.data.toLowerCase()).to.be.eq( @@ -79,14 +81,18 @@ describeWithExistingNode("Frontier RPC (Create Collection)", (context) => { }); step("create collection call can estimate gas", async function () { - const contract = new context.web3.eth.Contract(EVOLUTION_COLLETION_FACTORY_ABI, CONTRACT_ADDRESS, { - from: FAITH, - gasPrice: GAS_PRICE, - }); - + const contract = new context.web3.eth.Contract( + EVOLUTION_COLLECTION_FACTORY_ABI, + EVOLUTION_COLLECTION_FACTORY_CONTRACT_ADDRESS, + { + from: FAITH, + gasPrice: GAS_PRICE, + } + ); + let nonce = await context.web3.eth.getTransactionCount(FAITH); context.web3.eth.accounts.wallet.add(FAITH_PRIVATE_KEY); - + const estimatedGas = await contract.methods.createCollection(FAITH).estimateGas({ from: FAITH, gas: GAS_LIMIT, diff --git a/e2e-tests/tests/test-evolution.ts b/e2e-tests/tests/test-evolution.ts index bfecf3a6e..3569351be 100644 --- a/e2e-tests/tests/test-evolution.ts +++ b/e2e-tests/tests/test-evolution.ts @@ -1,248 +1,298 @@ -import { addressToCollectionId, createCollection, describeWithExistingNode, slotAndOwnerToTokenId } from "./util"; -import { GAS_LIMIT, FAITH, SELECTOR_LOG_EVOLVED_WITH_EXTERNAL_TOKEN_URI, SELECTOR_LOG_MINTED_WITH_EXTERNAL_TOKEN_URI, SELECTOR_LOG_OWNERSHIP_TRANSFERRED, SELECTOR_LOG_PUBLIC_MINTING_ENABLED, SELECTOR_LOG_PUBLIC_MINTING_DISABLED, ALITH, ALITH_PRIVATE_KEY, EVOLUTION_COLLECTION_ABI } from "./config"; +import { + addressToCollectionId, + createCollection, + describeWithExistingNode, + extractRevertReason, + slotAndOwnerToTokenId, +} from "./util"; +import { + GAS_LIMIT, + FAITH, + SELECTOR_LOG_EVOLVED_WITH_EXTERNAL_TOKEN_URI, + SELECTOR_LOG_MINTED_WITH_EXTERNAL_TOKEN_URI, + SELECTOR_LOG_OWNERSHIP_TRANSFERRED, + SELECTOR_LOG_PUBLIC_MINTING_ENABLED, + SELECTOR_LOG_PUBLIC_MINTING_DISABLED, + ALITH, + ALITH_PRIVATE_KEY, +} from "./config"; import { expect } from "chai"; import Contract from "web3-eth-contract"; import BN from "bn.js"; import { step } from "mocha-steps"; describeWithExistingNode("Frontier RPC (Mint and Evolve Assets)", (context) => { - let collectionContract: Contract - - beforeEach(async function () { - collectionContract = await createCollection(context); - }); - - step("when collection does not exist token uri should fail", async function () { - const tokenId = "0"; - - try { - await collectionContract.methods.tokenURI(tokenId).call(); - expect.fail("Expected error was not thrown"); // Ensure an error is thrown - } catch (error) { - expect(error.message).to.be.eq( - "Returned error: VM Exception while processing transaction: revert asset does not exist" - ); - } - }); - - step("when asset is minted it should return token uri", async function () { - const slot = "0"; - const to = FAITH; - const tokenURI = "https://example.com"; - - let nonce = await context.web3.eth.getTransactionCount(FAITH); - const result = await collectionContract.methods.mintWithExternalURI(to, slot, tokenURI).send({ from: FAITH, gas: GAS_LIMIT, nonce: nonce++ }); - expect(result.status).to.be.eq(true); - - const tokenId = result.events.MintedWithExternalURI.returnValues._tokenId; - const got = await collectionContract.methods.tokenURI(tokenId).call(); - expect(got).to.be.eq(tokenURI); - }); - - step("given slot and owner it should return token id", async function () { - const slot = "1"; - const to = FAITH; - - const tokenId = slotAndOwnerToTokenId(slot, to); - expect(tokenId).to.be.eq("000000000000000000000001c0f0f4ab324c46e55d02d0033343b4be8a55532d"); - const tokenIdDecimal = new BN(tokenId, 16, "be").toString(10); - expect(tokenIdDecimal).to.be.eq("2563001357829637001682277476112176020532353127213"); - }); - - step("when asset is minted it should emit an event", async function () { - const slot = "22"; - const to = FAITH; - const tokenURI = "https://example.com"; - - const result = await collectionContract.methods.mintWithExternalURI(to, slot, tokenURI) - .send({ from: FAITH, gas: GAS_LIMIT }); - expect(result.status).to.be.eq(true); - - expect(Object.keys(result.events).length).to.be.eq(1); - - // data returned within the event - expect(result.events.MintedWithExternalURI.returnValues._to).to.be.eq(to); - expect(result.events.MintedWithExternalURI.returnValues._slot).to.be.eq(slot); - expect(result.events.MintedWithExternalURI.returnValues._tokenURI).to.be.eq(tokenURI); - const tokenId = slotAndOwnerToTokenId(slot, to); - const tokenIdDecimal = new BN(tokenId, 16, "be").toString(10); - expect(result.events.MintedWithExternalURI.returnValues._tokenId).to.be.eq(tokenIdDecimal); - - // event topics - expect(result.events.MintedWithExternalURI.raw.topics.length).to.be.eq(2); - expect(result.events.MintedWithExternalURI.raw.topics[0]).to.be.eq(SELECTOR_LOG_MINTED_WITH_EXTERNAL_TOKEN_URI); - expect(result.events.MintedWithExternalURI.raw.topics[1]).to.be.eq(context.web3.utils.padLeft(FAITH.toLowerCase(), 64)); - - // event data - expect(result.events.MintedWithExternalURI.raw.data).to.be.eq( - context.web3.eth.abi.encodeParameters( - ["uint96", "uint256", "string"], - [slot, tokenIdDecimal, tokenURI] - ) - ); - }); - - step("when asset is evolved it should change token uri", async function () { - const slot = "22"; - const to = FAITH; - const tokenURI = "https://example.com"; - const newTokenURI = "https://new_example.com"; - const tokenId = slotAndOwnerToTokenId(slot, to); - const tokenIdDecimal = new BN(tokenId, 16, "be").toString(10); - - const mintingResult = await collectionContract.methods.mintWithExternalURI(to, slot, tokenURI).send({ from: FAITH, gas: GAS_LIMIT }); - expect(mintingResult.status).to.be.eq(true); - - const evolvingResult = await collectionContract.methods.evolveWithExternalURI(tokenIdDecimal, newTokenURI).send({ from: FAITH, gas: GAS_LIMIT }); - expect(evolvingResult.status).to.be.eq(true); - - const got = await collectionContract.methods.tokenURI(tokenIdDecimal).call(); - expect(got).to.be.eq(newTokenURI); - }); - - step("when asset is evolved it should emit an event", async function () { - const slot = "22"; - const to = FAITH; - const tokenURI = "https://example.com"; - const newTokenURI = "https://new_example.com"; - const tokenId = slotAndOwnerToTokenId(slot, to); - const tokenIdDecimal = new BN(tokenId, 16, "be").toString(10); - - const mintingResult = await collectionContract.methods.mintWithExternalURI(to, slot, tokenURI).send({ from: FAITH, gas: GAS_LIMIT }); - expect(mintingResult.status).to.be.eq(true); - - const evolvingResult = await collectionContract.methods.evolveWithExternalURI(tokenIdDecimal, newTokenURI).send({ from: FAITH, gas: GAS_LIMIT }); - expect(evolvingResult.status).to.be.eq(true); - - expect(Object.keys(evolvingResult.events).length).to.be.eq(1); - - // data returned within the event - expect(evolvingResult.events.EvolvedWithExternalURI.returnValues._tokenId).to.be.eq(tokenIdDecimal); - expect(evolvingResult.events.EvolvedWithExternalURI.returnValues._tokenURI).to.be.eq(newTokenURI); - - // event topics - expect(evolvingResult.events.EvolvedWithExternalURI.raw.topics.length).to.be.eq(2); - expect(evolvingResult.events.EvolvedWithExternalURI.raw.topics[0]).to.be.eq(SELECTOR_LOG_EVOLVED_WITH_EXTERNAL_TOKEN_URI); - expect(evolvingResult.events.EvolvedWithExternalURI.raw.topics[1]).to.be.eq("0x" + tokenId); - - // event data - expect(evolvingResult.events.EvolvedWithExternalURI.raw.data).to.be.eq( - context.web3.eth.abi.encodeParameters( - ["string"], - [newTokenURI] - ) - ); - }); - - + let collectionContract: Contract; + + beforeEach(async function () { + collectionContract = await createCollection(context); + }); + + step("when collection does not exist token uri should fail", async function () { + const tokenId = "0"; + + try { + await collectionContract.methods.tokenURI(tokenId).call(); + expect.fail("Expected error was not thrown"); // Ensure an error is thrown + } catch (error) { + expect(error.message).to.be.eq( + "Returned error: VM Exception while processing transaction: revert asset does not exist" + ); + } + }); + + step("when asset is minted it should return token uri", async function () { + const slot = "0"; + const to = FAITH; + const tokenURI = "https://example.com"; + + let nonce = await context.web3.eth.getTransactionCount(FAITH); + const result = await collectionContract.methods + .mintWithExternalURI(to, slot, tokenURI) + .send({ from: FAITH, gas: GAS_LIMIT, nonce: nonce++ }); + expect(result.status).to.be.eq(true); + + const tokenId = result.events.MintedWithExternalURI.returnValues._tokenId; + const got = await collectionContract.methods.tokenURI(tokenId).call(); + expect(got).to.be.eq(tokenURI); + }); + + step("given slot and owner it should return token id", async function () { + const slot = "1"; + const to = FAITH; + + const tokenId = slotAndOwnerToTokenId(slot, to); + expect(tokenId).to.be.eq("000000000000000000000001c0f0f4ab324c46e55d02d0033343b4be8a55532d"); + const tokenIdDecimal = new BN(tokenId, 16, "be").toString(10); + expect(tokenIdDecimal).to.be.eq("2563001357829637001682277476112176020532353127213"); + }); + + step("when asset is minted it should emit an event", async function () { + const slot = "22"; + const to = FAITH; + const tokenURI = "https://example.com"; + + const result = await collectionContract.methods + .mintWithExternalURI(to, slot, tokenURI) + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(result.status).to.be.eq(true); + + expect(Object.keys(result.events).length).to.be.eq(1); + + // data returned within the event + expect(result.events.MintedWithExternalURI.returnValues._to).to.be.eq(to); + expect(result.events.MintedWithExternalURI.returnValues._slot).to.be.eq(slot); + expect(result.events.MintedWithExternalURI.returnValues._tokenURI).to.be.eq(tokenURI); + const tokenId = slotAndOwnerToTokenId(slot, to); + const tokenIdDecimal = new BN(tokenId, 16, "be").toString(10); + expect(result.events.MintedWithExternalURI.returnValues._tokenId).to.be.eq(tokenIdDecimal); + + // event topics + expect(result.events.MintedWithExternalURI.raw.topics.length).to.be.eq(2); + expect(result.events.MintedWithExternalURI.raw.topics[0]).to.be.eq(SELECTOR_LOG_MINTED_WITH_EXTERNAL_TOKEN_URI); + expect(result.events.MintedWithExternalURI.raw.topics[1]).to.be.eq( + context.web3.utils.padLeft(FAITH.toLowerCase(), 64) + ); + + // event data + expect(result.events.MintedWithExternalURI.raw.data).to.be.eq( + context.web3.eth.abi.encodeParameters(["uint96", "uint256", "string"], [slot, tokenIdDecimal, tokenURI]) + ); + }); + + step("when asset is evolved it should change token uri", async function () { + const slot = "22"; + const to = FAITH; + const tokenURI = "https://example.com"; + const newTokenURI = "https://new_example.com"; + const tokenId = slotAndOwnerToTokenId(slot, to); + const tokenIdDecimal = new BN(tokenId, 16, "be").toString(10); + + const mintingResult = await collectionContract.methods + .mintWithExternalURI(to, slot, tokenURI) + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(mintingResult.status).to.be.eq(true); + + const evolvingResult = await collectionContract.methods + .evolveWithExternalURI(tokenIdDecimal, newTokenURI) + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(evolvingResult.status).to.be.eq(true); + + const got = await collectionContract.methods.tokenURI(tokenIdDecimal).call(); + expect(got).to.be.eq(newTokenURI); + }); + + step("when asset is evolved it should emit an event", async function () { + const slot = "22"; + const to = FAITH; + const tokenURI = "https://example.com"; + const newTokenURI = "https://new_example.com"; + const tokenId = slotAndOwnerToTokenId(slot, to); + const tokenIdDecimal = new BN(tokenId, 16, "be").toString(10); + + const mintingResult = await collectionContract.methods + .mintWithExternalURI(to, slot, tokenURI) + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(mintingResult.status).to.be.eq(true); + + const evolvingResult = await collectionContract.methods + .evolveWithExternalURI(tokenIdDecimal, newTokenURI) + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(evolvingResult.status).to.be.eq(true); + + expect(Object.keys(evolvingResult.events).length).to.be.eq(1); + + // data returned within the event + expect(evolvingResult.events.EvolvedWithExternalURI.returnValues._tokenId).to.be.eq(tokenIdDecimal); + expect(evolvingResult.events.EvolvedWithExternalURI.returnValues._tokenURI).to.be.eq(newTokenURI); + + // event topics + expect(evolvingResult.events.EvolvedWithExternalURI.raw.topics.length).to.be.eq(2); + expect(evolvingResult.events.EvolvedWithExternalURI.raw.topics[0]).to.be.eq( + SELECTOR_LOG_EVOLVED_WITH_EXTERNAL_TOKEN_URI + ); + expect(evolvingResult.events.EvolvedWithExternalURI.raw.topics[1]).to.be.eq("0x" + tokenId); + + // event data + expect(evolvingResult.events.EvolvedWithExternalURI.raw.data).to.be.eq( + context.web3.eth.abi.encodeParameters(["string"], [newTokenURI]) + ); + }); }); describeWithExistingNode("@qa Frontier RPC (Public Minting)", (context) => { - let collectionContract: Contract - - beforeEach(async function () { - collectionContract = await createCollection(context); - }); - - step("public minting is disabled by default and when is deactivated event is emitted", async function () { - // is disable - expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(false); - // disable twice has no effect - const disablingPublicMintingResult = await collectionContract.methods.disablePublicMinting().send({ from: FAITH, gas: GAS_LIMIT }); - expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(false); - // event is emitted anyway - expect(disablingPublicMintingResult.status).to.be.eq(true); - expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(false); - expect(Object.keys(disablingPublicMintingResult.events).length).to.be.eq(1); - expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.topics.length).to.be.eq(1); - expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.topics[0]).to.be.eq(SELECTOR_LOG_PUBLIC_MINTING_DISABLED); - expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.data).to.be.eq('0x'); - }); - - step("enable public minting emits an event", async function () { - const enablingPublicMintingResult = await collectionContract.methods.enablePublicMinting().send({ from: FAITH, gas: GAS_LIMIT }); - expect(enablingPublicMintingResult.status).to.be.eq(true); - expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(true); - - expect(Object.keys(enablingPublicMintingResult.events).length).to.be.eq(1); - expect(enablingPublicMintingResult.events.PublicMintingEnabled.raw.topics.length).to.be.eq(1); - expect(enablingPublicMintingResult.events.PublicMintingEnabled.raw.topics[0]).to.be.eq(SELECTOR_LOG_PUBLIC_MINTING_ENABLED); - expect(enablingPublicMintingResult.events.PublicMintingEnabled.raw.data).to.be.eq('0x'); - - // enable twice has no effect - await collectionContract.methods.enablePublicMinting().send({ from: FAITH, gas: GAS_LIMIT }); - expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(true); - }); - - step("I can mint even I'm not the owner", async function () { - const enablingPublicMintingResult = await collectionContract.methods.enablePublicMinting().send({ from: FAITH, gas: GAS_LIMIT }); - expect(enablingPublicMintingResult.status).to.be.eq(true); - expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(true); - - const owner = await collectionContract.methods.owner().call(); - expect(owner).to.be.eq(FAITH); - - let nonce = await context.web3.eth.getTransactionCount(ALITH); - context.web3.eth.accounts.wallet.add(ALITH_PRIVATE_KEY); - collectionContract.options.from = ALITH - const mintingResult = await collectionContract.methods.mintWithExternalURI(ALITH, "123", "some/random/token/uri").send({ from: ALITH, gas: GAS_LIMIT, nonce: nonce++ }); - expect(mintingResult.status).to.be.eq(true); - }); - - step("after enabling I can disable", async function () { - const disablingPublicMintingResult = await collectionContract.methods.disablePublicMinting().send({ from: FAITH, gas: GAS_LIMIT }); - expect(disablingPublicMintingResult.status).to.be.eq(true); - expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(false); - expect(Object.keys(disablingPublicMintingResult.events).length).to.be.eq(1); - expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.topics.length).to.be.eq(1); - expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.topics[0]).to.be.eq(SELECTOR_LOG_PUBLIC_MINTING_DISABLED); - expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.data).to.be.eq('0x'); - }); - - step("after changing owner I can't disable", async function () { - await collectionContract.methods.transferOwnership("0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac").send({ from: FAITH, gas: GAS_LIMIT }); - try { - await collectionContract.methods.disablePublicMinting().send({ from: FAITH, gas: GAS_LIMIT }); - expect.fail("Expected error was not thrown"); // Ensure an error is thrown - } catch (error) { - // console.log(error.message); - } - }); + let collectionContract: Contract; + + beforeEach(async function () { + collectionContract = await createCollection(context); + }); + + step("public minting is disabled by default and when is deactivated event is emitted", async function () { + // is disable + expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(false); + // disable twice has no effect + const disablingPublicMintingResult = await collectionContract.methods + .disablePublicMinting() + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(false); + // event is emitted anyway + expect(disablingPublicMintingResult.status).to.be.eq(true); + expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(false); + expect(Object.keys(disablingPublicMintingResult.events).length).to.be.eq(1); + expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.topics.length).to.be.eq(1); + expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.topics[0]).to.be.eq( + SELECTOR_LOG_PUBLIC_MINTING_DISABLED + ); + expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.data).to.be.eq("0x"); + }); + + step("enable public minting emits an event", async function () { + const enablingPublicMintingResult = await collectionContract.methods + .enablePublicMinting() + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(enablingPublicMintingResult.status).to.be.eq(true); + expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(true); + + expect(Object.keys(enablingPublicMintingResult.events).length).to.be.eq(1); + expect(enablingPublicMintingResult.events.PublicMintingEnabled.raw.topics.length).to.be.eq(1); + expect(enablingPublicMintingResult.events.PublicMintingEnabled.raw.topics[0]).to.be.eq( + SELECTOR_LOG_PUBLIC_MINTING_ENABLED + ); + expect(enablingPublicMintingResult.events.PublicMintingEnabled.raw.data).to.be.eq("0x"); + + // enable twice has no effect + await collectionContract.methods.enablePublicMinting().send({ from: FAITH, gas: GAS_LIMIT }); + expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(true); + }); + + step("I can mint even I'm not the owner", async function () { + const enablingPublicMintingResult = await collectionContract.methods + .enablePublicMinting() + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(enablingPublicMintingResult.status).to.be.eq(true); + expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(true); + + const owner = await collectionContract.methods.owner().call(); + expect(owner).to.be.eq(FAITH); + + let nonce = await context.web3.eth.getTransactionCount(ALITH); + context.web3.eth.accounts.wallet.add(ALITH_PRIVATE_KEY); + collectionContract.options.from = ALITH; + const mintingResult = await collectionContract.methods + .mintWithExternalURI(ALITH, "123", "some/random/token/uri") + .send({ from: ALITH, gas: GAS_LIMIT, nonce: nonce++ }); + expect(mintingResult.status).to.be.eq(true); + }); + + step("after enabling I can disable", async function () { + const disablingPublicMintingResult = await collectionContract.methods + .disablePublicMinting() + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(disablingPublicMintingResult.status).to.be.eq(true); + expect(await collectionContract.methods.isPublicMintingEnabled().call()).to.be.eq(false); + expect(Object.keys(disablingPublicMintingResult.events).length).to.be.eq(1); + expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.topics.length).to.be.eq(1); + expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.topics[0]).to.be.eq( + SELECTOR_LOG_PUBLIC_MINTING_DISABLED + ); + expect(disablingPublicMintingResult.events.PublicMintingDisabled.raw.data).to.be.eq("0x"); + }); + + step("after changing owner I can't disable", async function () { + await collectionContract.methods + .transferOwnership("0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac") + .send({ from: FAITH, gas: GAS_LIMIT }); + try { + await collectionContract.methods.disablePublicMinting().send({ from: FAITH, gas: GAS_LIMIT }); + expect.fail("Expected error was not thrown"); // Ensure an error is thrown + } catch (error) { + expect(await extractRevertReason(context, error.receipt.transactionHash)).to.eq("NoPermission"); + } + }); }); describeWithExistingNode("@qa Frontier RPC (Transfer Ownership)", (context) => { - let collectionContract: Contract - - before(async function () { - collectionContract = await createCollection(context); - }); - - step("when is transferred owner should change and emit an event", async function () { - const newOwner = "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"; - - expect(await collectionContract.methods.owner().call()).to.be.eq(FAITH); - const tranferringResult = await collectionContract.methods.transferOwnership(newOwner).send({ from: FAITH, gas: GAS_LIMIT }); - expect(tranferringResult.status).to.be.eq(true); - expect(await collectionContract.methods.owner().call()).to.be.eq(newOwner); - - expect(Object.keys(tranferringResult.events).length).to.be.eq(1); - - // data returned within the event - expect(tranferringResult.events.OwnershipTransferred.returnValues._previousOwner).to.be.eq(FAITH); - expect(tranferringResult.events.OwnershipTransferred.returnValues._newOwner).to.be.eq(newOwner); - - // event topics - expect(tranferringResult.events.OwnershipTransferred.raw.topics.length).to.be.eq(3); - expect(tranferringResult.events.OwnershipTransferred.raw.topics[0]).to.be.eq(SELECTOR_LOG_OWNERSHIP_TRANSFERRED); - expect(tranferringResult.events.OwnershipTransferred.raw.topics[1]).to.be.eq(context.web3.utils.padLeft(FAITH.toLowerCase(), 64)); - expect(tranferringResult.events.OwnershipTransferred.raw.topics[2]).to.be.eq(context.web3.utils.padLeft(newOwner.toLowerCase(), 64)); - // event data - expect(tranferringResult.events.OwnershipTransferred.raw.data).to.be.eq('0x'); - - try { - await collectionContract.methods.transferOwnership(FAITH).send({ from: FAITH, gas: GAS_LIMIT }); - expect.fail("Expected error was not thrown"); // Ensure an error is thrown - } catch (error) { } - - }); -}); \ No newline at end of file + let collectionContract: Contract; + + before(async function () { + collectionContract = await createCollection(context); + }); + + step("when is transferred owner should change and emit an event", async function () { + const newOwner = "0xf24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"; + + expect(await collectionContract.methods.owner().call()).to.be.eq(FAITH); + const tranferringResult = await collectionContract.methods + .transferOwnership(newOwner) + .send({ from: FAITH, gas: GAS_LIMIT }); + expect(tranferringResult.status).to.be.eq(true); + expect(await collectionContract.methods.owner().call()).to.be.eq(newOwner); + + expect(Object.keys(tranferringResult.events).length).to.be.eq(1); + + // data returned within the event + expect(tranferringResult.events.OwnershipTransferred.returnValues._previousOwner).to.be.eq(FAITH); + expect(tranferringResult.events.OwnershipTransferred.returnValues._newOwner).to.be.eq(newOwner); + + // event topics + expect(tranferringResult.events.OwnershipTransferred.raw.topics.length).to.be.eq(3); + expect(tranferringResult.events.OwnershipTransferred.raw.topics[0]).to.be.eq( + SELECTOR_LOG_OWNERSHIP_TRANSFERRED + ); + expect(tranferringResult.events.OwnershipTransferred.raw.topics[1]).to.be.eq( + context.web3.utils.padLeft(FAITH.toLowerCase(), 64) + ); + expect(tranferringResult.events.OwnershipTransferred.raw.topics[2]).to.be.eq( + context.web3.utils.padLeft(newOwner.toLowerCase(), 64) + ); + // event data + expect(tranferringResult.events.OwnershipTransferred.raw.data).to.be.eq("0x"); + + try { + await collectionContract.methods.transferOwnership(FAITH).send({ from: FAITH, gas: GAS_LIMIT }); + expect.fail("Expected error was not thrown"); // Ensure an error is thrown + } catch (error) { + expect(await extractRevertReason(context, error.receipt.transactionHash)).to.eq("NoPermission"); + } + }); +}); diff --git a/e2e-tests/tests/test-update-extended-token-uri.ts b/e2e-tests/tests/test-update-extended-token-uri.ts index 2e944555e..5ac4ec512 100644 --- a/e2e-tests/tests/test-update-extended-token-uri.ts +++ b/e2e-tests/tests/test-update-extended-token-uri.ts @@ -15,7 +15,7 @@ import { describeWithExistingNode } from "./util"; describeWithExistingNode("@qa Frontier RPC (Extend Token URI)", (context) => { let contract: Contract; - + before(async function () { contract = new context.web3.eth.Contract(ASSET_METADATA_EXTENDER_ABI, ASSET_METADATA_EXTENDER_ADDRESS, { from: FAITH, @@ -24,16 +24,16 @@ describeWithExistingNode("@qa Frontier RPC (Extend Token URI)", (context) => { }); context.web3.eth.accounts.wallet.add(FAITH_PRIVATE_KEY); }); - + let uloc = `universal/location_${Date.now()}`; let extendResult: any; let tokenURI = "https://example.com"; - + step("by default token uri is empty", async function () { expect(await contract.methods.balanceOfUL(uloc).call()).to.be.eq("0"); expect(await contract.methods.hasExtensionByClaimer(uloc, FAITH).call()).to.be.eq(false); }); - + step("extend should return ok", async function () { let nonce = await context.web3.eth.getTransactionCount(FAITH); extendResult = await contract.methods.extendULWithExternalURI(uloc, tokenURI).send({ @@ -57,13 +57,17 @@ describeWithExistingNode("@qa Frontier RPC (Extend Token URI)", (context) => { // data returned within the event expect(Object.keys(extendResult.events).length).to.be.eq(1); expect(extendResult.events.ExtendedULWithExternalURI.returnValues._claimer).to.be.eq(FAITH); - expect(extendResult.events.ExtendedULWithExternalURI.returnValues._universalLocationHash).to.be.eq(context.web3.utils.soliditySha3(uloc)); + expect(extendResult.events.ExtendedULWithExternalURI.returnValues._universalLocationHash).to.be.eq( + context.web3.utils.soliditySha3(uloc) + ); expect(extendResult.events.ExtendedULWithExternalURI.returnValues._universalLocation).to.be.eq(uloc); expect(extendResult.events.ExtendedULWithExternalURI.returnValues._tokenURI).to.be.eq(tokenURI); // event topics expect(extendResult.events.ExtendedULWithExternalURI.raw.topics.length).to.be.eq(3); - expect(extendResult.events.ExtendedULWithExternalURI.raw.topics[0]).to.be.eq(SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI); + expect(extendResult.events.ExtendedULWithExternalURI.raw.topics[0]).to.be.eq( + SELECTOR_LOG_EXTENDED_UL_WITH_EXTERNAL_URI + ); expect(extendResult.events.ExtendedULWithExternalURI.raw.topics[1]).to.be.eq( context.web3.utils.padLeft(FAITH.toLowerCase(), 64) ); @@ -73,13 +77,9 @@ describeWithExistingNode("@qa Frontier RPC (Extend Token URI)", (context) => { // event data expect(extendResult.events.ExtendedULWithExternalURI.raw.data).to.be.eq( - context.web3.eth.abi.encodeParameters( - ["string", "string"], - [uloc, tokenURI] - ) + context.web3.eth.abi.encodeParameters(["string", "string"], [uloc, tokenURI]) ); }); - }); describeWithExistingNode("Frontier RPC (Update Extended Token URI)", async (context) => { @@ -108,7 +108,7 @@ describeWithExistingNode("Frontier RPC (Update Extended Token URI)", async (cont }); expect(createResult.status).to.be.eq(true); }); - + step("check existing extension", async function () { expect(await contract.methods.extensionOfULByIndex(uloc, 0).call()).to.be.eq(tokenURI); expect(await contract.methods.extensionOfULByClaimer(uloc, FAITH).call()).to.be.eq(tokenURI); @@ -141,13 +141,21 @@ describeWithExistingNode("Frontier RPC (Update Extended Token URI)", async (cont // data returned within the event expect(Object.keys(updateExtensionResult.events).length).to.be.eq(1); expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.returnValues._claimer).to.be.eq(FAITH); - expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.returnValues._universalLocationHash).to.be.eq(context.web3.utils.soliditySha3(uloc)); - expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.returnValues._universalLocation).to.be.eq(uloc); - expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.returnValues._tokenURI).to.be.eq(newTokenURI); + expect( + updateExtensionResult.events.UpdatedExtendedULWithExternalURI.returnValues._universalLocationHash + ).to.be.eq(context.web3.utils.soliditySha3(uloc)); + expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.returnValues._universalLocation).to.be.eq( + uloc + ); + expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.returnValues._tokenURI).to.be.eq( + newTokenURI + ); // event topics expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.raw.topics.length).to.be.eq(3); - expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.raw.topics[0]).to.be.eq(SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI); + expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.raw.topics[0]).to.be.eq( + SELECTOR_LOG_UPDATED_EXTENDED_UL_WITH_EXTERNAL_URI + ); expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.raw.topics[1]).to.be.eq( context.web3.utils.padLeft(FAITH.toLowerCase(), 64) ); @@ -157,10 +165,7 @@ describeWithExistingNode("Frontier RPC (Update Extended Token URI)", async (cont // event data expect(updateExtensionResult.events.UpdatedExtendedULWithExternalURI.raw.data).to.be.eq( - context.web3.eth.abi.encodeParameters( - ["string", "string"], - [uloc, newTokenURI] - ) + context.web3.eth.abi.encodeParameters(["string", "string"], [uloc, newTokenURI]) ); }); }); diff --git a/e2e-tests/tests/test-vesting.ts b/e2e-tests/tests/test-vesting.ts new file mode 100644 index 000000000..7818f9b5c --- /dev/null +++ b/e2e-tests/tests/test-vesting.ts @@ -0,0 +1,61 @@ +import { expect } from "chai"; +import { step } from "mocha-steps"; +import Contract from "web3-eth-contract"; +import { + VESTING_CONTRACT_ADDRESS, + VESTING_ABI, + GAS_LIMIT, + GAS_PRICE, + FAITH, + FAITH_PRIVATE_KEY, + ALITH, + ALITH_PRIVATE_KEY, +} from "./config"; +import { describeWithExistingNode, extractRevertReason } from "./util"; +import Web3 from "web3"; + +describeWithExistingNode("Frontier RPC (Vesting)", (context) => { + let contract: Contract; + + before(async function () { + contract = new context.web3.eth.Contract(VESTING_ABI, VESTING_CONTRACT_ADDRESS, { + from: FAITH, + gasPrice: GAS_PRICE, + gas: GAS_LIMIT, + }); + context.web3.eth.accounts.wallet.add(FAITH_PRIVATE_KEY); + context.web3.eth.accounts.wallet.add(ALITH_PRIVATE_KEY); + }); + + it("when there is no vesting it returns empty list", async function () { + const vesting = await contract.methods.vesting(FAITH).call(); + expect(vesting).to.deep.eq([]); + }); + it("when there is no vesting do vest reverts", async function () { + try { + let nonce = await context.web3.eth.getTransactionCount(FAITH); + await contract.methods.vest().send({ from: FAITH, gas: GAS_LIMIT, nonce: nonce++ }); + expect.fail("Expected error was not thrown"); // Ensure an error is thrown + } catch (error) { + expect(await extractRevertReason(context, error.receipt.transactionHash)).to.eq("NotVesting"); + } + }); + it("when vesting exists it returns the list", async function () { + const vesting = await contract.methods.vesting(ALITH).call(); + expect(vesting).to.deep.eq([ + ["799999000000000000000000000", "7999990000000000000000000", "0"], + ["799999500000000000000000000", "3999997500000000000000000", "0"], + ]); + }); + step("when vesting exists do vest returns ok", async function () { + let nonce = await context.web3.eth.getTransactionCount(ALITH); + contract.options.from = ALITH; + let result = await contract.methods.vest().send({ from: ALITH, gas: GAS_LIMIT, nonce: nonce++ }); + expect(result.status).to.be.eq(true); + }); + step("when vesting exists do vestOther returns ok", async function () { + let nonce = await context.web3.eth.getTransactionCount(FAITH); + let result = await contract.methods.vestOther(ALITH).send({ from: FAITH, gas: GAS_LIMIT, nonce: nonce++ }); + expect(result.status).to.be.eq(true); + }); +}); diff --git a/e2e-tests/tests/util.ts b/e2e-tests/tests/util.ts index 99ae6ec59..62ea900fd 100644 --- a/e2e-tests/tests/util.ts +++ b/e2e-tests/tests/util.ts @@ -2,7 +2,17 @@ import { ethers } from "ethers"; import Contract from "web3-eth-contract"; import Web3 from "web3"; import { JsonRpcResponse } from "web3-core-helpers"; -import { CONTRACT_ADDRESS, GAS_LIMIT, GAS_PRICE, FAITH, FAITH_PRIVATE_KEY, EVOLUTION_COLLETION_FACTORY_ABI, EVOLUTION_COLLECTION_ABI, MAX_U96, LOCAL_NODE_URL } from "./config"; +import { + EVOLUTION_COLLECTION_FACTORY_CONTRACT_ADDRESS, + GAS_LIMIT, + GAS_PRICE, + FAITH, + FAITH_PRIVATE_KEY, + EVOLUTION_COLLECTION_FACTORY_ABI, + EVOLUTION_COLLECTION_ABI, + MAX_U96, + LOCAL_NODE_URL, +} from "./config"; import BN from "bn.js"; import { expect } from "chai"; @@ -47,11 +57,15 @@ export function describeWithExistingNode(title: string, cb: (context: { web3: We } export async function createCollection(context: { web3: Web3 }): Promise { - const contract = new context.web3.eth.Contract(EVOLUTION_COLLETION_FACTORY_ABI, CONTRACT_ADDRESS, { - from: FAITH, - gasPrice: GAS_PRICE, - }); - + const contract = new context.web3.eth.Contract( + EVOLUTION_COLLECTION_FACTORY_ABI, + EVOLUTION_COLLECTION_FACTORY_CONTRACT_ADDRESS, + { + from: FAITH, + gasPrice: GAS_PRICE, + } + ); + let nonce = await context.web3.eth.getTransactionCount(FAITH); context.web3.eth.accounts.wallet.add(FAITH_PRIVATE_KEY); const result = await contract.methods.createCollection(FAITH).send({ @@ -62,13 +76,17 @@ export async function createCollection(context: { web3: Web3 }): Promise laos_runtime::RuntimeGenesisConfig { .collect(), ..Default::default() }, + vesting: laos_runtime::VestingConfig { + vesting: vec![ + (predefined_accounts::ALITH.into(), 0, 100, 1000 * UNIT), + (predefined_accounts::ALITH.into(), 0, 200, 500 * UNIT), + ], + }, ..Default::default() } } diff --git a/pallets/precompiles-benchmark/Cargo.toml b/pallets/precompiles-benchmark/Cargo.toml new file mode 100644 index 000000000..35f0454a2 --- /dev/null +++ b/pallets/precompiles-benchmark/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "pallet-precompiles-benchmark" +description = "A pallet whose purpose is to benchmark precompile calls" +version = "0.1.0" +homepage = "https://freeverse.io" +edition = "2021" +license = "MIT-0" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +parity-scale-codec = { workspace = true, features = ["derive"] } +scale-info = { workspace = true, features = ["derive"] } +frame-support = { workspace = true } +frame-system = { workspace = true } +sp-runtime = { workspace = true } +sp-core = { workspace = true } +frame-benchmarking = { optional = true, workspace = true } +sp-std = { workspace = true } +fp-evm = { workspace = true } +precompile-utils = { workspace = true, default-features = false } +pallet-evm = { workspace = true } +pallet-vesting ={ workspace = true } +# Runtime primitives +laos-primitives = { workspace = true } + +[dev-dependencies] +sp-io = { workspace = true } +sp-runtime = { workspace = true } +precompile-utils = { workspace = true, features = ["testing"] } +pallet-balances = { workspace = true, features = ["std", "insecure_zero_ed"] } +pallet-timestamp = { workspace = true } +hex = { workspace = true } +test-utils = { workspace = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-core/std", + "sp-runtime/std", + "frame-benchmarking/std", + "sp-std/std", + "fp-evm/std", + "precompile-utils/std", + "pallet-evm/std", + "pallet-vesting/std", + "laos-primitives/std" +] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] + +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] \ No newline at end of file diff --git a/pallets/precompiles-benchmark/src/lib.rs b/pallets/precompiles-benchmark/src/lib.rs new file mode 100644 index 000000000..d5eede58c --- /dev/null +++ b/pallets/precompiles-benchmark/src/lib.rs @@ -0,0 +1,77 @@ +// Copyright 2023-2024 Freeverse.io +// This file is part of LAOS. + +// LAOS is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// LAOS is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with LAOS. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] +use sp_core::H160; +use sp_runtime::traits::{Convert, ConvertBack}; + +pub use pallet::*; +pub mod precompiles; +pub mod weights; + +#[frame_support::pallet] +pub mod pallet { + use crate::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + #[pallet::no_default_bounds] + /// Converts `Self::AccountId` to `H160` + type AccountIdToH160: ConvertBack; + + #[pallet::no_default] + /// Gas weight mapping + type GasWeightMapping: pallet_evm::GasWeightMapping; + + /// WeightInfo of the calls + type WeightInfo: crate::weights::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(_); + + pub mod config_preludes { + use super::*; + use frame_support::{ + derive_impl, pallet_prelude::inject_runtime_type, register_default_impl, + }; + + pub struct TestDefaultConfig; + type AccountId = H160; + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::pallet::DefaultConfig, no_aggregated_types)] + impl frame_system::DefaultConfig for TestDefaultConfig {} + + #[register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + type AccountIdToH160 = AccountIdToH160; + type WeightInfo = (); + } + + pub struct AccountIdToH160; + impl Convert for AccountIdToH160 { + fn convert(account_id: AccountId) -> H160 { + H160(account_id.0) + } + } + + impl ConvertBack for AccountIdToH160 { + fn convert_back(account_id: H160) -> AccountId { + AccountId::from(account_id) + } + } + } +} diff --git a/pallets/precompiles-benchmark/src/precompiles/mod.rs b/pallets/precompiles-benchmark/src/precompiles/mod.rs new file mode 100644 index 000000000..3e4036aca --- /dev/null +++ b/pallets/precompiles-benchmark/src/precompiles/mod.rs @@ -0,0 +1,20 @@ +pub mod vesting; + +use fp_evm::ExitError; +use frame_support::pallet_prelude::Weight; +use pallet_evm::GasWeightMapping; +use precompile_utils::prelude::PrecompileHandle; + +pub fn register_cost( + handle: &mut impl PrecompileHandle, + weight: Weight, +) -> Result<(), ExitError> { + let required_gas = Runtime::GasWeightMapping::weight_to_gas(weight); + let remaining_gas = handle.remaining_gas(); + if required_gas > remaining_gas { + return Err(ExitError::OutOfGas); + } + handle.record_cost(required_gas)?; + handle.record_external_cost(Some(weight.ref_time()), Some(weight.proof_size()))?; + Ok(()) +} diff --git a/pallets/precompiles-benchmark/src/precompiles/vesting/benchmarking.rs b/pallets/precompiles-benchmark/src/precompiles/vesting/benchmarking.rs new file mode 100644 index 000000000..26d2e3a22 --- /dev/null +++ b/pallets/precompiles-benchmark/src/precompiles/vesting/benchmarking.rs @@ -0,0 +1,242 @@ +// Copyright 2023-2024 Freeverse.io +// This file is part of LAOS. + +// LAOS is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// LAOS is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with LAOS. If not, see . + +//! Benchmarking setup for pallet-living-assets-evolution +#![cfg(feature = "runtime-benchmarks")] +use super::*; + +use crate::pallet::{Config, Pallet}; +#[allow(unused)] +use fp_evm::Transfer; +use frame_benchmarking::v2::*; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use pallet_evm::{Context, ExitError, ExitReason, Log, PrecompileHandle}; +use pallet_vesting::Pallet as PalletVesting; +use precompile_utils::prelude::Address; +use sp_core::{Get, H160, H256, U256}; +use sp_runtime::{ + traits::{Convert, ConvertBack}, + Saturating, +}; +use sp_std::{vec, vec::Vec}; + +pub struct MockHandle { + pub input: Vec, + pub gas_limit: Option, + pub context: Context, + pub is_static: bool, + pub gas_used: u64, + pub logs: Vec, + pub code_address: H160, +} + +impl MockHandle { + pub fn new(caller: H160) -> Self { + Self { + input: vec![], + gas_limit: None, + context: Context { address: H160::zero(), caller, apparent_value: U256::zero() }, + is_static: false, + gas_used: 0, + logs: vec![], + code_address: H160::zero(), + } + } +} + +impl PrecompileHandle for MockHandle { + /// Perform subcall in provided context. + /// Precompile specifies in which context the subcall is executed. + fn call( + &mut self, + _: H160, + _: Option, + _: Vec, + _: Option, + _: bool, + _: &Context, + ) -> (ExitReason, Vec) { + unimplemented!() + } + + fn record_cost(&mut self, cost: u64) -> Result<(), ExitError> { + self.gas_used += cost; + Ok(()) + } + + fn record_external_cost(&mut self, _: Option, _: Option) -> Result<(), ExitError> { + Ok(()) + } + + fn refund_external_cost(&mut self, _: Option, _: Option) {} + + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { + let log = Log { address, topics, data }; + self.logs.push(log); + Ok(()) + } + + fn remaining_gas(&self) -> u64 { + 1000000000000 + } + + fn code_address(&self) -> H160 { + self.code_address + } + + fn input(&self) -> &[u8] { + &self.input + } + + fn context(&self) -> &Context { + &self.context + } + + fn is_static(&self) -> bool { + self.is_static + } + + fn gas_limit(&self) -> Option { + self.gas_limit + } +} + +type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +/* + * Allow directive added because when the macro expands, `T` has constraints in + * multiple locations. This is what the expanded code looks like: + * ``` + * fn _precompile_vest(verify: bool) + * where + * T: Config + pallet_vesting: Config, + * ``` + */ +#[allow(clippy::multiple_bound_locations)] +#[benchmarks( + where + T: Config + pallet_vesting::Config, + T::AccountIdToH160: ConvertBack, + BalanceOf: Into, + BlockNumberFor: Into +)] +mod benchmarks { + use super::*; + use frame_support::traits::tokens::currency::Currency; + + #[benchmark] + fn precompile_vest() { + let caller: T::AccountId = whitelisted_caller(); + let caller_origin = T::RuntimeOrigin::from(RawOrigin::from(Some(caller.clone()))); + let min_transfer = T::MinVestedTransfer::get(); + let _ = T::Currency::issue(min_transfer); + T::Currency::make_free_balance_be(&caller, min_transfer); + + let target: T::AccountId = account("target", 0, 1); + let mut handle = MockHandle::new(T::AccountIdToH160::convert(target.clone())); + let target_lookup = T::Lookup::unlookup(target.clone()); + let starting_block = 0u32; + let per_block = min_transfer; + + PalletVesting::::vested_transfer( + caller_origin, + target_lookup, + pallet_vesting::VestingInfo::new(min_transfer, per_block, starting_block.into()), + ) + .unwrap(); + + #[block] + { + VestingPrecompile::::vest(&mut handle).unwrap(); + } + } + + #[benchmark] + fn precompile_vest_other() { + let caller: T::AccountId = whitelisted_caller(); + let caller_origin = T::RuntimeOrigin::from(RawOrigin::from(Some(caller.clone()))); + let mut handle = MockHandle::new(T::AccountIdToH160::convert(caller.clone())); + let min_transfer = T::MinVestedTransfer::get(); + let _ = T::Currency::issue(min_transfer); + T::Currency::make_free_balance_be(&caller, min_transfer); + + let target: T::AccountId = account("target", 0, 1); + let target_lookup = T::Lookup::unlookup(target.clone()); + let starting_block = 0u32; + let per_block = min_transfer; + + PalletVesting::::vested_transfer( + caller_origin, + target_lookup, + pallet_vesting::VestingInfo::new(min_transfer, per_block, starting_block.into()), + ) + .unwrap(); + + #[block] + { + VestingPrecompile::::vest_other( + &mut handle, + Address::from(T::AccountIdToH160::convert(target)), + ) + .unwrap(); + } + } + + #[benchmark] + fn precompile_vesting(m: Linear<0, { ::MAX_VESTING_SCHEDULES }>) { + let caller: T::AccountId = whitelisted_caller(); + let caller_origin = T::RuntimeOrigin::from(RawOrigin::from(Some(caller.clone()))); + let mut handle = MockHandle::new(T::AccountIdToH160::convert(caller.clone())); + let min_transfer = T::MinVestedTransfer::get(); + let total_transferrable = min_transfer.saturating_mul(m.into()); + + let _ = T::Currency::issue(total_transferrable); + T::Currency::make_free_balance_be(&caller, total_transferrable); + + let target: T::AccountId = account("target", 0, 1); + let target_lookup = T::Lookup::unlookup(target.clone()); + let starting_block = 0u32; + let per_block = min_transfer; + + for i in 1..m + 1 { + PalletVesting::::vested_transfer( + caller_origin.clone(), + target_lookup.clone(), + pallet_vesting::VestingInfo::new( + min_transfer, + per_block.saturating_mul(i.into()), + starting_block.into(), + ), + ) + .unwrap(); + } + + #[block] + { + assert_eq!( + VestingPrecompile::::vesting( + &mut handle, + Address::from(T::AccountIdToH160::convert(target)), + ) + .unwrap() + .len(), + m as usize + ); + } + } +} diff --git a/pallets/precompiles-benchmark/src/precompiles/vesting/contracts/Vesting.json b/pallets/precompiles-benchmark/src/precompiles/vesting/contracts/Vesting.json new file mode 100644 index 000000000..c62f8c222 --- /dev/null +++ b/pallets/precompiles-benchmark/src/precompiles/vesting/contracts/Vesting.json @@ -0,0 +1,58 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_target", + "type": "address" + } + ], + "name": "vesting", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "locked", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "perBlock", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "startingBlock", + "type": "uint256" + } + ], + "internalType": "struct Vesting.VestingInfo[]", + "name": "", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_target", + "type": "address" + } + ], + "name": "vestOther", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/pallets/precompiles-benchmark/src/precompiles/vesting/contracts/Vesting.sol b/pallets/precompiles-benchmark/src/precompiles/vesting/contracts/Vesting.sol new file mode 100644 index 000000000..acd7b082f --- /dev/null +++ b/pallets/precompiles-benchmark/src/precompiles/vesting/contracts/Vesting.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity >=0.8.3; + +/// @title Pallet Vesting Interface (pallet code here: https://github.com/paritytech/polkadot-sdk/tree/master/substrate/frame/vesting) +/// @author LAOS Team +/// @notice This interface allows Solidity contracts to interact with pallet-vesting +/// @custom:address 0x0000000000000000000000000000000000000406 +interface Vesting { + + /// @dev Defines the vesting info + /// @dev `locked` is the amount of locked tokens + /// @dev `perBlock` is the amount of unlocking tokens per block + /// @dev `startingBlock` is the block number at which the tokens start unlocking + struct VestingInfo { + uint256 locked; + uint256 perBlock; + uint256 startingBlock; + } + + /// @notice Returns the vesting info of an account + /// @param _target The address of the account the vesting data should be returned for + /// @return The vesting info of the target account, as an array of `VestingInfo` structs + function vesting(address _target) external view returns (VestingInfo[] memory); + + /// @notice Unlock the vested funds of the caller + /// @dev Reverts if the caller doesn't have any vested funds + function vest() external; + + /// @notice Unlock vested funds for the target account + /// @dev Reverts if the target account doesn't have any vested funds + /// @param _target The address for which funds will be unlocked + function vestOther(address _target) external; +} diff --git a/pallets/precompiles-benchmark/src/precompiles/vesting/mock.rs b/pallets/precompiles-benchmark/src/precompiles/vesting/mock.rs new file mode 100644 index 000000000..b148b9706 --- /dev/null +++ b/pallets/precompiles-benchmark/src/precompiles/vesting/mock.rs @@ -0,0 +1,206 @@ +// Copyright 2023-2024 Freeverse.io +// This file is part of LAOS. + +// LAOS is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// LAOS is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with LAOS. If not, see . + +use core::str::FromStr; +use sp_runtime::BuildStorage; + +use precompile_utils::precompile_set::{AddressU64, PrecompileAt, PrecompileSetBuilder}; + +use crate::{ + pallet, + precompiles::vesting::{VestingPrecompile, VestingPrecompileCall}, +}; +use frame_support::{ + derive_impl, parameter_types, + traits::{FindAuthor, OnFinalize, OnInitialize, WithdrawReasons}, + weights::constants::RocksDbWeight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_core::{H160, U256}; +use sp_runtime::{ + traits::{ConvertInto, IdentityLookup}, + ConsensusEngineId, +}; +pub use test_utils::*; + +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Vesting: pallet_vesting, + VestingWrapper: pallet, + Timestamp: pallet_timestamp::{Pallet, Call, Storage}, + EVM: pallet_evm::{Pallet, Call, Storage, Config, Event}, + } +); + +pub type AccountId = H160; +type Balance = u128; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type AccountData = pallet_balances::AccountData; + type DbWeight = RocksDbWeight; +} + +parameter_types! { + pub const MaxTokenUriLength: u32 = 512; +} + +#[derive_impl(pallet::config_preludes::TestDefaultConfig as pallet::DefaultConfig)] +impl pallet::Config for Test { + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 0; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for Test { + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type RuntimeHoldReason = (); + type DustRemoval = (); +} + +parameter_types! { + pub const MinVestedTransfer: u64 = 1; + pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = + WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); +} + +impl pallet_vesting::Config for Test { + type BlockNumberToBalance = ConvertInto; + type Currency = Balances; + type MinVestedTransfer = MinVestedTransfer; + type RuntimeEvent = RuntimeEvent; + type UnvestedFundsAllowedWithdrawReasons = UnvestedFundsAllowedWithdrawReasons; + type WeightInfo = (); + const MAX_VESTING_SCHEDULES: u32 = 100; +} + +parameter_types! { + pub const MinimumPeriod: u64 = 1000; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); +} + +pub struct FindAuthorTruncated; +impl FindAuthor for FindAuthorTruncated { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(H160::from_str("1234500000000000000000000000000000000000").unwrap()) + } +} + +pub const BLOCK_GAS_LIMIT: u64 = 15_000_000; +pub const MAX_POV_SIZE: u64 = 5 * 1024 * 1024; + +pub type PrecompileCall = VestingPrecompileCall; + +pub type LaosPrecompiles = + PrecompileSetBuilder, VestingPrecompile>,)>; + +frame_support::parameter_types! { + pub BlockGasLimit: U256 = U256::from(BLOCK_GAS_LIMIT); + pub const GasLimitPovSizeRatio: u64 = BLOCK_GAS_LIMIT.saturating_div(MAX_POV_SIZE); + /// 1 weight to 1 gas, for testing purposes + pub WeightPerGas: frame_support::weights::Weight = frame_support::weights::Weight::from_parts(1, 0); + pub PrecompilesInstance: LaosPrecompiles = LaosPrecompiles::new(); +} + +impl pallet_evm::Config for Test { + type FeeCalculator = (); + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightPerGas = WeightPerGas; + type BlockHashMapping = pallet_evm::SubstrateBlockHashMapping; + type CallOrigin = pallet_evm::EnsureAddressRoot; + type WithdrawOrigin = pallet_evm::EnsureAddressNever; + type AddressMapping = pallet_evm::IdentityAddressMapping; + type Currency = Balances; + type RuntimeEvent = RuntimeEvent; + type PrecompilesType = LaosPrecompiles; + type PrecompilesValue = PrecompilesInstance; + type ChainId = (); + type BlockGasLimit = BlockGasLimit; + type Runner = pallet_evm::runner::stack::Runner; + type OnChargeTransaction = (); + type OnCreate = (); + type FindAuthor = FindAuthorTruncated; + type GasLimitPovSizeRatio = GasLimitPovSizeRatio; + type Timestamp = Timestamp; + type WeightInfo = (); +} + +#[derive(Default)] +pub(crate) struct ExtBuilder { + /// Endowed accounts with balances + balances: Vec<(AccountId, Balance)>, +} + +impl ExtBuilder { + /// Fund some accounts before starting the test + pub(crate) fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self { + self.balances = balances; + self + } + + /// Build the test externalities for use in tests + pub(crate) fn build(self) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default() + .build_storage() + .expect("Frame system builds valid default genesis config"); + + pallet_balances::GenesisConfig:: { balances: self.balances.clone() } + .assimilate_storage(&mut t) + .expect("Pallet balances storage can be assimilated"); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext + } +} + +pub type BlockNumber = BlockNumberFor; + +/// Rolls to the desired block. Returns the number of blocks played. +pub(crate) fn roll_to(n: BlockNumber) -> BlockNumber { + let mut num_blocks = 0; + let mut block = System::block_number(); + while block < n { + roll_one_block!(false); + block = System::block_number(); + num_blocks += 1; + } + num_blocks +} diff --git a/pallets/precompiles-benchmark/src/precompiles/vesting/mod.rs b/pallets/precompiles-benchmark/src/precompiles/vesting/mod.rs new file mode 100644 index 000000000..517b2bbdf --- /dev/null +++ b/pallets/precompiles-benchmark/src/precompiles/vesting/mod.rs @@ -0,0 +1,145 @@ +// Copyright 2023-2024 Freeverse.io +// This file is part of LAOS. + +// LAOS is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// LAOS is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with LAOS. If not, see . + +//! LAOS precompile module. + +#![cfg_attr(not(feature = "std"), no_std)] +pub use crate::{precompiles::register_cost, weights::WeightInfo}; +use frame_support::{traits::tokens::currency::Currency, DefaultNoBound}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use pallet_vesting::Pallet as PalletVesting; +use precompile_utils::prelude::{revert, solidity, Address, EvmResult, PrecompileHandle}; +use scale_info::prelude::{format, string::String}; +use sp_core::{H160, U256}; +use sp_runtime::{ + traits::{ConvertBack, PhantomData, StaticLookup}, + DispatchError, +}; +use sp_std::vec::Vec; + +type BalanceOf = <::Currency as Currency< + ::AccountId, +>>::Balance; + +#[derive(Default, solidity::Codec)] +pub struct VestingInfo { + locked: U256, + per_block: U256, + starting_block: U256, +} + +#[derive(Clone, DefaultNoBound)] +pub struct VestingPrecompile(PhantomData); + +impl VestingPrecompile { + pub fn new() -> Self { + Self(PhantomData) + } +} + +#[precompile_utils::precompile] +impl VestingPrecompile +where + Runtime: crate::Config + pallet_vesting::Config, + Runtime::AccountIdToH160: ConvertBack, + BalanceOf: Into, + BlockNumberFor: Into, +{ + #[precompile::public("vesting(address)")] + #[precompile::view] + pub fn vesting( + handle: &mut impl PrecompileHandle, + account: Address, + ) -> EvmResult> { + match PalletVesting::::vesting(Runtime::AccountIdToH160::convert_back( + account.into(), + )) { + Some(v) => { + register_cost::( + handle, + ::WeightInfo::precompile_vesting(v.len() as u32), + )?; + let mut output: Vec = Vec::with_capacity(v.len()); + + for i in v { + output.push(VestingInfo { + locked: i.locked().into(), + per_block: i.per_block().into(), + starting_block: i.starting_block().into(), + }) + } + + Ok(output) + }, + None => { + register_cost::( + handle, + ::WeightInfo::precompile_vesting(0), + )?; + Ok(Vec::new()) + }, + } + } + + #[precompile::public("vest()")] + pub fn vest(handle: &mut impl PrecompileHandle) -> EvmResult<()> { + register_cost::( + handle, + ::WeightInfo::precompile_vest(), + )?; + + match PalletVesting::::vest( + ::RuntimeOrigin::from(RawOrigin::from(Some( + Runtime::AccountIdToH160::convert_back(handle.context().caller), + ))), + ) { + Ok(_) => Ok(()), + Err(err) => Err(revert(convert_dispatch_error_to_string(err))), + } + } + + #[precompile::public("vestOther(address)")] + pub fn vest_other(handle: &mut impl PrecompileHandle, account: Address) -> EvmResult<()> { + register_cost::( + handle, + ::WeightInfo::precompile_vest_other(), + )?; + + let origin = ::RuntimeOrigin::from(RawOrigin::from(Some( + Runtime::AccountIdToH160::convert_back(handle.context().caller), + ))); + let account_id = Runtime::AccountIdToH160::convert_back(account.into()); + let target = + <::Lookup as StaticLookup>::unlookup(account_id); + match PalletVesting::::vest_other(origin, target) { + Ok(_) => Ok(()), + Err(err) => Err(revert(convert_dispatch_error_to_string(err))), + } + } +} + +fn convert_dispatch_error_to_string(err: DispatchError) -> String { + match err { + DispatchError::Module(mod_err) => mod_err.message.unwrap_or("Unknown module error").into(), + _ => format!("{:?}", err), + } +} + +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; diff --git a/pallets/precompiles-benchmark/src/precompiles/vesting/tests.rs b/pallets/precompiles-benchmark/src/precompiles/vesting/tests.rs new file mode 100644 index 000000000..83810d148 --- /dev/null +++ b/pallets/precompiles-benchmark/src/precompiles/vesting/tests.rs @@ -0,0 +1,207 @@ +// Copyright 2023-2024 Freeverse.io +// This file is part of LAOS. + +// LAOS is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// LAOS is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with LAOS. If not, see . + +//! Living assets precompile tests. + +use super::*; +use frame_support::assert_ok; +use mock::*; +use pallet_vesting::{Pallet, VestingInfo as VestingInfoPallet}; +use precompile_utils::testing::{Alice, Bob, Precompile1, PrecompileTesterExt}; +use sp_core::H160; + +/// Get precompiles from the mock. +fn precompiles() -> LaosPrecompiles { + PrecompilesInstance::get() +} + +#[test] +fn selectors() { + assert!(PrecompileCall::vest_selectors().contains(&0x458EFDE3)); + assert!(PrecompileCall::vest_other_selectors().contains(&0x55E60C8)); + assert!(PrecompileCall::vesting_selectors().contains(&0xE388C423)); +} + +#[test] +fn vesting_for_account_with_no_vesting_returns_empty_vec() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::vesting { account: Address(Alice.into()) }, + ) + .execute_returns(Vec::::new()) + }); +} + +#[test] +fn vesting_for_account_with_one_vesting_returns_vesting_info_vec() { + ExtBuilder::default() + .with_balances(vec![(Bob.into(), 100u128)]) + .build() + .execute_with(|| { + let locked = 100; + let per_block = 10; + let starting_block = 0; + assert_ok!(Pallet::::vested_transfer( + RuntimeOrigin::signed(Bob.into()), + Alice.into(), + VestingInfoPallet::new(locked, per_block, starting_block), + )); + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::vesting { account: Address(Alice.into()) }, + ) + .execute_returns(vec![VestingInfo { + locked: locked.into(), + per_block: per_block.into(), + starting_block: starting_block.into(), + }]) + }); +} + +#[test] +fn vesting_for_account_with_two_vestings_returns_vesting_info_vec() { + ExtBuilder::default() + .with_balances(vec![(Bob.into(), 1000u128)]) + .build() + .execute_with(|| { + let locked = 100; + let per_block = 10; + let starting_block = 0; + + assert_ok!(Pallet::::vested_transfer( + RuntimeOrigin::signed(Bob.into()), + Alice.into(), + VestingInfoPallet::new(locked, per_block, starting_block), + )); + assert_ok!(Pallet::::vested_transfer( + RuntimeOrigin::signed(Bob.into()), + Alice.into(), + VestingInfoPallet::new(locked, per_block, starting_block), + )); + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::vesting { account: Address(Alice.into()) }, + ) + .expect_cost(56898396) + .execute_returns(vec![ + VestingInfo { + locked: locked.into(), + per_block: per_block.into(), + starting_block: starting_block.into(), + }, + VestingInfo { + locked: locked.into(), + per_block: per_block.into(), + starting_block: starting_block.into(), + }, + ]) + }); +} + +#[test] +fn vest_reverts_no_vested_funds() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test(Alice, Precompile1, PrecompileCall::vest {}) + .execute_reverts(|r| r == b"NotVesting"); + }); +} + +#[test] +fn vest_increases_usable_balance() { + ExtBuilder::default() + .with_balances(vec![(Bob.into(), 100u128)]) + .build() + .execute_with(|| { + let locked = 10; + let per_block = 1; + let starting_block = 0; + let end_block = 5u32; + + assert_ok!(Pallet::::vested_transfer( + RuntimeOrigin::signed(Bob.into()), + Alice.into(), + VestingInfoPallet::new(locked, per_block, starting_block), + )); + assert_eq!( + Balances::usable_balance(H160::from(Alice)), + 1, + "1 free balance because 1 block has passed" + ); + roll_to(end_block.into()); + precompiles() + .prepare_test(Alice, Precompile1, PrecompileCall::vest {}) + .expect_cost(472000000) + .execute_some(); + + assert_eq!(Balances::usable_balance(H160::from(Alice)), end_block as u128); + }); +} + +#[test] +fn vest_other_reverts_no_vested_funds() { + ExtBuilder::default().build().execute_with(|| { + precompiles() + .prepare_test( + Alice, + Precompile1, + PrecompileCall::vest_other { account: Address(Bob.into()) }, + ) + .execute_reverts(|r| r == b"NotVesting"); + }); +} + +#[test] +fn vest_other_increases_other_usable_balance() { + ExtBuilder::default() + .with_balances(vec![(Bob.into(), 100u128)]) + .build() + .execute_with(|| { + let locked = 10; + let per_block = 1; + let starting_block = 0; + let end_block = 5u32; + + assert_ok!(Pallet::::vested_transfer( + RuntimeOrigin::signed(Bob.into()), + Alice.into(), + VestingInfoPallet::new(locked, per_block, starting_block), + )); + assert_eq!( + Balances::usable_balance(H160::from(Alice)), + 1, + "1 free balance because 1 block has passed" + ); + roll_to(end_block.into()); + precompiles() + .prepare_test( + Bob, + Precompile1, + PrecompileCall::vest_other { account: Address(Alice.into()) }, + ) + .expect_cost(472000000) + .execute_some(); + + assert_eq!(Balances::usable_balance(H160::from(Alice)), end_block as u128); + }); +} diff --git a/pallets/precompiles-benchmark/src/weights.rs b/pallets/precompiles-benchmark/src/weights.rs new file mode 100644 index 000000000..0ff74b4f5 --- /dev/null +++ b/pallets/precompiles-benchmark/src/weights.rs @@ -0,0 +1,145 @@ + +//! Autogenerated weights for `pallet_precompiles_benchmark` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2024-07-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Luigis-MacBook-Pro.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` + +// Executed Command: +// ./target/release/laos +// benchmark +// pallet +// --pallet +// pallet-precompiles-benchmark +// --extrinsic=* +// --output +// precompiles/vesting/src/weights.rs +// --execution +// wasm +// --wasm-execution=compiled +// --steps +// 50 +// --repeat +// 20 +// --template +// ./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_precompiles_benchmark`. +pub trait WeightInfo { + fn precompile_vest() -> Weight; + fn precompile_vest_other() -> Weight; + fn precompile_vesting(m: u32, ) -> Weight; +} + +/// Weights for `pallet_precompiles_benchmark` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Vesting::Vesting` (r:1 w:1) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1045), added: 3520, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1287), added: 3762, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(887), added: 3362, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + fn precompile_vest() -> Weight { + // Proof Size summary in bytes: + // Measured: `520` + // Estimated: `4752` + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(47_000_000, 4752) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Vesting::Vesting` (r:1 w:1) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1045), added: 3520, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1287), added: 3762, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(887), added: 3362, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + fn precompile_vest_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `520` + // Estimated: `4752` + // Minimum execution time: 45_000_000 picoseconds. + Weight::from_parts(47_000_000, 4752) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: `Vesting::Vesting` (r:1 w:0) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1045), added: 3520, mode: `MaxEncodedLen`) + /// The range of component `m` is `[0, 28]`. + fn precompile_vesting(m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `198 + m * (36 ±0)` + // Estimated: `4510` + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(6_809_346, 4510) + // Standard Error: 3_204 + .saturating_add(Weight::from_parts(44_525, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `Vesting::Vesting` (r:1 w:1) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1045), added: 3520, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1287), added: 3762, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(887), added: 3362, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + fn precompile_vest() -> Weight { + // Proof Size summary in bytes: + // Measured: `520` + // Estimated: `4752` + // Minimum execution time: 46_000_000 picoseconds. + Weight::from_parts(47_000_000, 4752) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `Vesting::Vesting` (r:1 w:1) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1045), added: 3520, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1287), added: 3762, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(887), added: 3362, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + fn precompile_vest_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `520` + // Estimated: `4752` + // Minimum execution time: 45_000_000 picoseconds. + Weight::from_parts(47_000_000, 4752) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: `Vesting::Vesting` (r:1 w:0) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1045), added: 3520, mode: `MaxEncodedLen`) + /// The range of component `m` is `[0, 28]`. + fn precompile_vesting(m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `198 + m * (36 ±0)` + // Estimated: `4510` + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(6_809_346, 4510) + // Standard Error: 3_204 + .saturating_add(Weight::from_parts(44_525, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } +} \ No newline at end of file diff --git a/runtime/laos/Cargo.toml b/runtime/laos/Cargo.toml index 35268e2dc..163a02572 100644 --- a/runtime/laos/Cargo.toml +++ b/runtime/laos/Cargo.toml @@ -58,6 +58,7 @@ sp-staking = { workspace = true } pallet-asset-metadata-extender = { workspace = true } pallet-laos-evolution = { workspace = true } +pallet-precompiles-benchmark = { workspace = true } # Polkadot @@ -145,6 +146,7 @@ std = [ "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", "pallet-vesting/std", + "pallet-precompiles-benchmark/std", "pallet-transaction-payment/std", "pallet-xcm/std", "parachain-info/std", @@ -219,6 +221,7 @@ runtime-benchmarks = [ "staging-xcm-executor/runtime-benchmarks", "pallet-asset-metadata-extender/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", + "pallet-precompiles-benchmark/runtime-benchmarks", "pallet-parachain-staking/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-utility/runtime-benchmarks", @@ -255,6 +258,7 @@ try-runtime = [ "sp-runtime/try-runtime", "pallet-asset-metadata-extender/try-runtime", "pallet-vesting/try-runtime", + "pallet-precompiles-benchmark/try-runtime", "pallet-parachain-staking/try-runtime", "pallet-proxy/try-runtime", "pallet-utility/try-runtime", diff --git a/runtime/laos/src/benchmarks.rs b/runtime/laos/src/benchmarks.rs index acda0241c..f8dfdea2a 100644 --- a/runtime/laos/src/benchmarks.rs +++ b/runtime/laos/src/benchmarks.rs @@ -26,5 +26,6 @@ frame_benchmarking::define_benchmarks!( [pallet_evm, EVM] [pallet_laos_evolution, LaosEvolution] [pallet_asset_metadata_extender, AssetMetadataExtender] + [pallet_precompiles_benchmark, PrecompilesBenchmark] // TODO pallet_xcm? ); diff --git a/runtime/laos/src/configs/benchmark.rs b/runtime/laos/src/configs/benchmark.rs new file mode 100644 index 000000000..5aaf40510 --- /dev/null +++ b/runtime/laos/src/configs/benchmark.rs @@ -0,0 +1,24 @@ +// Copyright 2023-2024 Freeverse.io +// This file is part of LAOS. + +// LAOS is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// LAOS is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with LAOS. If not, see . + +use crate::{types::AccountIdToH160, weights, Runtime}; +use pallet_precompiles_benchmark::pallet::Config; + +impl Config for Runtime { + type AccountIdToH160 = AccountIdToH160; + type GasWeightMapping = pallet_evm::FixedGasWeightMapping; + type WeightInfo = weights::pallet_precompiles_benchmark::WeightInfo; +} diff --git a/runtime/laos/src/configs/mod.rs b/runtime/laos/src/configs/mod.rs index c94839d2d..4d76fb4d6 100644 --- a/runtime/laos/src/configs/mod.rs +++ b/runtime/laos/src/configs/mod.rs @@ -19,6 +19,7 @@ mod aura; mod authorship; mod balances; mod base_fee; +mod benchmark; mod cumulus_dmp_queue; mod cumulus_parachain_system; mod cumulus_xcmp_queue; diff --git a/runtime/laos/src/lib.rs b/runtime/laos/src/lib.rs index bc8678c1a..4598e9604 100644 --- a/runtime/laos/src/lib.rs +++ b/runtime/laos/src/lib.rs @@ -138,6 +138,7 @@ construct_runtime!( // LAOS pallets LaosEvolution: pallet_laos_evolution = 100, AssetMetadataExtender: pallet_asset_metadata_extender = 101, + PrecompilesBenchmark: pallet_precompiles_benchmark = 102, } ); diff --git a/runtime/laos/src/precompiles/mod.rs b/runtime/laos/src/precompiles/mod.rs index d6f5c4e2a..a96cb7065 100644 --- a/runtime/laos/src/precompiles/mod.rs +++ b/runtime/laos/src/precompiles/mod.rs @@ -30,6 +30,7 @@ use pallet_laos_evolution::{ }, ASSET_PRECOMPILE_ADDRESS_PREFIX, }; +use pallet_precompiles_benchmark::precompiles::vesting::VestingPrecompile; use precompile_utils::precompile_set::{ AcceptDelegateCall, AddressU64, CallableByContract, CallableByPrecompile, PrecompileAt, PrecompileSetBuilder, PrecompileSetStartingWith, PrecompilesInRangeInclusive, @@ -65,6 +66,11 @@ pub type LaosPrecompilesSetAt = ( AssetMetadataExtenderPrecompile, (CallableByContract, CallableByPrecompile), >, + PrecompileAt< + AddressU64<1030>, + VestingPrecompile, + (CallableByContract, CallableByPrecompile), + >, ); parameter_types! { diff --git a/runtime/laos/src/weights/mod.rs b/runtime/laos/src/weights/mod.rs index 55b5fd093..06e4e572b 100644 --- a/runtime/laos/src/weights/mod.rs +++ b/runtime/laos/src/weights/mod.rs @@ -26,6 +26,7 @@ pub mod pallet_evm; pub mod pallet_laos_evolution; pub mod pallet_multisig; pub mod pallet_parachain_staking; +pub mod pallet_precompiles_benchmark; pub mod pallet_proxy; pub mod pallet_session; pub mod pallet_sudo; diff --git a/runtime/laos/src/weights/pallet_precompiles_benchmark.rs b/runtime/laos/src/weights/pallet_precompiles_benchmark.rs new file mode 100644 index 000000000..9566c3b33 --- /dev/null +++ b/runtime/laos/src/weights/pallet_precompiles_benchmark.rs @@ -0,0 +1,83 @@ + +//! Autogenerated weights for `pallet_precompiles_benchmark` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2024-07-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Luigis-MacBook-Pro.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: 1024 + +// Executed Command: +// ./target/release/laos +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_precompiles_benchmark +// --extrinsic=* +// --wasm-execution=compiled +// --output=./runtime/laos/src/weights/ + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_precompiles_benchmark`. +pub struct WeightInfo(PhantomData); +impl pallet_precompiles_benchmark::weights::WeightInfo for WeightInfo { + /// Storage: `Vesting::Vesting` (r:1 w:1) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1045), added: 3520, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1287), added: 3762, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(887), added: 3362, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + fn precompile_vest() -> Weight { + // Proof Size summary in bytes: + // Measured: `520` + // Estimated: `4752` + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(39_000_000, 0) + .saturating_add(Weight::from_parts(0, 4752)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Vesting::Vesting` (r:1 w:1) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1045), added: 3520, mode: `MaxEncodedLen`) + /// Storage: `Balances::Locks` (r:1 w:1) + /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1287), added: 3762, mode: `MaxEncodedLen`) + /// Storage: `Balances::Freezes` (r:1 w:0) + /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(887), added: 3362, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(116), added: 2591, mode: `MaxEncodedLen`) + fn precompile_vest_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `520` + // Estimated: `4752` + // Minimum execution time: 38_000_000 picoseconds. + Weight::from_parts(39_000_000, 0) + .saturating_add(Weight::from_parts(0, 4752)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(3)) + } + /// Storage: `Vesting::Vesting` (r:1 w:0) + /// Proof: `Vesting::Vesting` (`max_values`: None, `max_size`: Some(1045), added: 3520, mode: `MaxEncodedLen`) + /// The range of component `m` is `[0, 28]`. + fn precompile_vesting(m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `198 + m * (36 ±0)` + // Estimated: `4510` + // Minimum execution time: 4_000_000 picoseconds. + Weight::from_parts(5_778_301, 0) + .saturating_add(Weight::from_parts(0, 4510)) + // Standard Error: 2_240 + .saturating_add(Weight::from_parts(53_144, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(1)) + } +} diff --git a/utils/test/src/lib.rs b/utils/test/src/lib.rs index 805f2679e..2e4e8feaf 100644 --- a/utils/test/src/lib.rs +++ b/utils/test/src/lib.rs @@ -21,21 +21,28 @@ /// Rolls to a block number by simulating the block production /// /// ```rs -/// roll_one_block!(true); +/// roll_one_block!(true); // staking enabled +/// roll_one_block!(false); // staking disabled /// ``` #[macro_export] macro_rules! roll_one_block { - ($staking_enabled: expr) => { + (true) => {{ Balances::on_finalize(System::block_number()); System::on_finalize(System::block_number()); System::set_block_number(System::block_number() + 1); System::reset_events(); System::on_initialize(System::block_number()); Balances::on_initialize(System::block_number()); - if $staking_enabled { - ParachainStaking::on_initialize(System::block_number()); - } - }; + ParachainStaking::on_initialize(System::block_number()); + }}; + (false) => {{ + Balances::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::reset_events(); + System::on_initialize(System::block_number()); + Balances::on_initialize(System::block_number()); + }}; } /// Asserts that some events were never emitted.