Verify Tx By Password
Introβ
This guide provides detailed instructions on how to use the "Verify tx" feature to gain write access to a smart contract without the need for the administrator's key, given knowledge of the password provided by the administrator.
Pre-requisites:
1. Setting up a new projectβ
Make sure you have a recent version of Node.js and npm installed,
Start by installing the aspect-tool:
npm install -g @artela/aspect-tool
Project Initialization, to kick off your project with aspect-tool, follow these steps:
# Create a new directory and navigate into it
mkdir verify-aspect&& cd verify-aspect
# Set up the npm project with aspect-tool 
aspect-tool init
# Install the necessary dependencies
npm install
This will create a project directory with the following structure:
.
βββ README.md
βββ asconfig.json
βββ aspect                   <-- Your aspect code resides here
βΒ Β  βββ index.ts       <-- Entry functions for the aspect
βββ contracts                <-- Place your smart contracts here
βββ package.json
βββ project.config.json
βββ scripts                  <-- Utility scripts, including deploying, binding and etc.
βΒ Β  βββ aspect-deploy.cjs
βΒ Β  βββ bind.cjs
βΒ Β  βββ contract-call.cjs
βΒ Β  βββ contract-deploy.cjs
βΒ Β  βββ contract-send.cjs
βΒ Β  βββ create-account.cjs
βββ tests
βββ tsconfig.json
2. Deploy a smart contractβ
2.1. Add a Smart Contractβ
Within the contracts directory of your project, create your smart contract source files with a .sol extension.
For example, create a Counter.sol file:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.2 <0.9.0;
contract Counter {
  uint256 private counter;
  address private owner;
  constructor() {
    owner = msg.sender;
  }
  function isOwner(address user) external view returns (bool result) {
    return user == owner;
  }
  modifier onlyOwner() {
    require(msg.sender == owner);
    _;
  }
  // only owner can do add
  function add(uint256 number) public onlyOwner {
    counter = counter + number;
  }
  function get() external view returns (uint256 result)  {
    return counter;
  }
}
2.2. Compile the Smart Contractβ
This step relies on solc, first check if solc is
installed correctly
solc --version
Compile your contract using:
npm run contract:build
β Successful compilation will generate
Counter.abiandCounter.binfiles in thebuild/contractdirectory.
2.3. Deploy the Smart Contractβ
2.3.1 Update project.config.jsonβ
Update the project.config.json in the root directory with the appropriate network configuration:
{
  "node": "https://betanet-rpc1.artela.network"
}
For more details regarding development environment setup, please refer to artela devnet
2.3.2 Create a blockchain account (optional).β
Execute the following command under the verify-aspect folder to create an account if you haven't already done so:
npm run account:create
β If an account gets created successfully, its private key will be dumped as
privateKey.txtin the current directory.
For more detailed usage information about this command, please refer to the create-account command documentation.
If your account lacks test tokens, join DiscordοΌand request some
in testnet-faucet channel.
2.3.4 Deploy your contractβ
Execute the following command within the verify-aspect folder, using the provided script:
npm run contract:deploy --  --abi ./build/contract/Counter.abi \
                           --bytecode ./build/contract/Counter.bin
β Upon successful deployment, the terminal will display the contract address.
For more detailed usage information about this command, please refer to the deploy-contract command documentation.
3. Create your Aspectβ
3.1. Implements an Aspectβ
In aspect/index.ts, add your Aspect to check the transaction, if validationData not equal Password, then revert:
import {
    allocate,
    entryPoint,
    execute,
    ITransactionVerifier,
    sys,
    TxVerifyInput, uint8ArrayToHex,
} from "@artela/aspect-libs";
class Aspect implements ITransactionVerifier {
    isOwner(sender: Uint8Array): bool {
        return true;
    }
    verifyTx(input: TxVerifyInput): Uint8Array {
        const Passwd: string = "123456";
        const validation = uint8ArrayToHex(input.validationData);
        // Verify whether the password matches the expected value.
        sys.require(validation == Passwd, 'invalid data');
        return sys.aspect.property.get<Uint8Array>("Owner");
    }
}
// 2.register aspect Instance
const aspect = new Aspect()
entryPoint.setAspect(aspect)
// 3.must export it
export {execute, allocate}
3.2. Compile the Aspectβ
Build your Aspect:
npm run aspect:build 
β The resulting
release.wasmin thebuildfolder contains the necessary WASM bytecode.
3.3. Deploy the Aspectβ
Deploy your compiled Aspect:
npm run aspect:deploy -- --wasm ./build/release.wasm \
                         --joinPoints VerifyTx \
                         --properties '[{"key":"Owner","value":"{owner-account}"}]' 
replace the placeholder {owner-account} with the real payment accounts. like: 0x08D721275c6DbB33bc688B62ef199bbd709154c9
β Upon successful execution, the terminal will display the
Aspect address. It is essential to make a note of this address as it will be useful later on.
For more detailed usage information about this command, please refer to the deploy-aspect command documentation.
4. Bind the Contract with the Aspectβ
Deploying the Aspect doesn't automatically activate it. To make it functional, bind it to a smart contract:
npm run contract:bind -- --contract {smart-contract-address} \
                         --abi ./build/contract/Counter.abi \
                         --aspectId {aspect-Id} 
- replace the placeholder {smart-contract-address} with the information obtained from
step 2.3.4 deploy the smart contract.
- replace the placeholder {aspect-Id} with the information obtained from step 3.4. Deploy the Aspect.
β The binding process has been successful, and the transaction receipt has been printed.
For more detailed usage information about this command, please refer to the bind-aspect command documentation.
5. Bind the EOA with the Aspectβ
npm run contract:bind -- --contract {owner-account} \
                         --abi ./build/contract/Counter.abi \
                         --aspectId {aspect-Id} \
                         --skfile {owner-private-key}
- replace the placeholder {owner-account} with the owner account address.
- replace the placeholder {owner-private-key} with the owner account private key file.
- replace the placeholder {aspect-Id} with the information obtained from step 3.4. Deploy the Aspect.
β The binding process has been successful, and the transaction receipt has been printed.
For more detailed usage information about this command, please refer to the bind-aspect command documentation.
6. Test the Smart Contract and Aspect Integrationβ
Within the scripts directory of your project,Create a Verify tx call script that generates a transaction that is not
signed by {owner} and uses the password provided by {owner} to make the transaction.
For example, create a verify.cjs file:
"use strict"
// import required libs
const fs = require('fs');
const Web3 = require('@artela/web3');
var argv = require('yargs')
    .string('node')
    .string('skfile')
    .array('args')
    .string('contract')
    .string('method')
    .string('abi')
    .string('password')
    .parserConfiguration({
        "parse-numbers": false,
    })
    .argv;
const {LegacyTransaction: EthereumTx} = require('@ethereumjs/tx')
const {numberToHex} = require("@artela/web3-utils");
async function call() {
    // init connection to Artela node
    const configJson = JSON.parse(fs.readFileSync('./project.config.json', "utf-8").toString());
    let node = (argv.node) ? String(argv.node) : configJson.node;
    if (!node) {
        console.log("'node' cannot be empty, please set by the parameter or artela.config.json")
        process.exit(0)
    }
    const web3 = new Web3(node);
    //--skfile ./build/privateKey.txt
    let senderPriKey = String(argv.skfile)
    if (!senderPriKey || senderPriKey === 'undefined') {
        senderPriKey = "privateKey.txt"
    }
    if (!fs.existsSync(senderPriKey)) {
        console.log("'account' cannot be empty, please set by the parameter ' --skfile ./build/privateKey.txt'")
        process.exit(0)
    }
    let pk = fs.readFileSync(senderPriKey, 'utf-8');
    let sender = web3.eth.accounts.privateKeyToAccount(pk.trim());
    console.log("from address: ", sender.address);
    web3.eth.accounts.wallet.add(sender.privateKey);
    // --contract 0x9999999999999999999999999999999999999999
    const contractAddr = argv.contract;
    if (!contractAddr) {
        console.log("'contract address' cannot be empty, please set by the parameter ' --contract 0x9999999999999999999999999999999999999999'")
        process.exit(0)
    }
    // --abi xxx/xxx.abi
    const abiPath = String(argv.abi)
    let abi = null
    if (abiPath && abiPath !== 'undefined') {
        abi = JSON.parse(fs.readFileSync(abiPath, "utf-8").toString());
    } else {
        console.log("'abi' cannot be empty, please set by the parameter' --abi xxx/xxx.abi'")
        process.exit(0)
    }
    // --args [55]
    const inputs = argv.args;
    let parameters = [];
    if (inputs && inputs !== 'undefined') {
        parameters = inputs;
    }
    //--method count
    const method = argv.method;
    if (!method || method === 'undefined') {
        console.log("'method' cannot be empty, please set by the parameter ' --method {method-name}'")
        process.exit(0)
    }
    let storageInstance = new web3.eth.Contract(abi, contractAddr);
    let contractCallData = await storageInstance.methods[method](...parameters).encodeABI();
    //--method count
    const password = argv.password;
    if (!password || password === 'undefined') {
        console.log("'password' cannot be empty, please set by the parameter ' --password {password}'")
        process.exit(0)
    }
    let encodedData = web3.eth.abi.encodeParameters(['bytes', 'bytes'],
        [password, contractCallData]);
    // Append magic prefix and checksum to the encoded data
    encodedData = '0xCAFECAFE' + web3.utils.keccak256(encodedData).slice(2, 10) + encodedData.slice(2);
    let nonce = await web3.eth.getTransactionCount("{owner-account}");
    let gasPrice = await web3.eth.getGasPrice();
    let chainId = await web3.eth.getChainId();
    // Set gas and gas limit
    let gas = 8000000;
    let gasLimit = 20000000;
    // Update the transaction object with the encoded data
    let tx =
        {
            from: sender.address,
            nonce: numberToHex(nonce),
            gasPrice: numberToHex(gasPrice),
            gasLimit: numberToHex(gasLimit),
            gas: numberToHex(gas),
            data: encodedData,
            to: contractAddr,
            chainId: numberToHex(chainId)
        }
    // Return the serialized unsigned transaction
    let rawTx = '0x' + bytesToHex(EthereumTx.fromTxData(tx).serialize());
    let receipt = await web3.eth.sendSignedTransaction(rawTx);
    console.log(`call contract with result: `);
    console.log(receipt);
}
function bytesToHex(bytes) {
    return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), '');
}
call().then();
- replace the placeholder {owner-account} with the owner account address.
This script is used to create an unsigned Ethereum transaction with validation data.
Then create a new account:
npm run account:create  -- --skfile ./test_account.txt
β If an account gets created successfully, its private key will be dumped as
test_account.txtin the current directory. And the Address will be printed, and you need to node it.
Now call verify.cjs with test_account to test.
node scripts/verify.cjs --skfile ./test_account.txt \
    --contract  {smart-contract-address} \
    --abi ./build/contract/Counter.abi \
    --args 1000 \
    --method add \
    --password 0x123456
- replace the placeholder {smart-contract-address} with the information obtained from
step 2.3.4 Deploy your contract.
If successful,it will print the result like this:
call contract with result:
{
blockHash: '0x40208524a15ba7d65a91fb4e7c06f87e5ac1276d...',
blockNumber: 271998,
contractAddress: null,
cumulativeGasUsed: 10000000,
from: '0x08d721275c6dbb33bc688b62ef199bb...',
gasUsed: 20000000,
logs: [],
logsBloom: '0x00000000000000000000000000000000000000000000000000...',
status: true,
to: '0xd52bd5b358de33fc126ff50e845973c...',
transactionHash: '0x7ea944b07825e0afbb6924246dcfbeef2da13ae0d2970...',
transactionIndex: 0,
type: '0x0'
}
Now let's check if the counter value in the contract has changed;
npm run contract:call -- --contract {smart-contract-address} \
                        --abi ./build/contract/Counter.abi \
                        --method get \
                        --skfile ./test_account.txt
- replace the placeholder {smart-contract-address} with the information obtained from
step 2.3.4 Deploy your contract.
If the command is executed successfully, will see
 ==== reuslt===1000
Congratulations! You've learned the basics of Aspect development. For a deeper dive, refer to our comprehensive Aspect Doc.