CUBE3 Documentation
  • CUBE3 Documentation
  • Detect Products
    • Inspector
    • Monitor
    • Sonar
      • Sonar Feeds
  • Protect Products
    • RASP
      • Quick Start
        • ERC-20 Non-Upgradeable (Foundry)
        • ERC-20 Non-Upgradeable (Hardhat)
        • ERC-20 Upgradeable (Foundry)
        • ERC-20 Upgradeable (Hardhat)
      • RASP Integration
        • 1. Installation
        • 2. Integration
        • 3. Deployment
        • 4. Registration
        • 5. Interaction
        • 6. Inspection
      • CUBE3 Protocol
      • CUBE3 SDK
  • Manage Products
    • Transactions
    • Alerts
      • Email Integration
      • Slack Integration
      • Telegram Integration
      • Discord Integration
      • Webhook Integration
    • Rules
  • API Documentation
    • Inspector API
    • Validation API
      • Troubleshooting
    • Management API
      • Monitor API
      • Alert API
      • Control lists API
    • Authentication
    • API Rate Limits
  • Risk Engine
    • Risk Scoring Introduction
    • Risk Scoring Detailed Overview
  • Settings
    • Billing
    • Organization
    • API Keys
  • Supported Blockchain Networks
    • CUBE3 Detect Products
    • CUBE3 Protect Products
  • Testing Guide
Powered by GitBook
On this page
  • Contents
  • Writing ERC-20 contract
  • Protecting transfer and mint methods
  • Final implementation of CUBE3 Protected ERC-20 contract
  • 2. Deployment
  • 1. Registering with CUBE3 backend
  • 2. Registering with CUBE3 Protocol (on-chain)
  • Interacting with your protected contract
  • Contents
  • 1. Creating cube3_transaction_executer nodejs application
  • 2. Installing necessary packages
  • 3. Setup contract object
  • 4. Payload generation
  • 5. Interacting with your protected functions
  1. Protect Products
  2. RASP
  3. Quick Start

ERC-20 Non-Upgradeable (Hardhat)

PreviousERC-20 Non-Upgradeable (Foundry)NextERC-20 Upgradeable (Foundry)

Last updated 8 months ago

Contents

  1. a.

    b.

  2. a.

    b.

Writing ERC-20 contract

  1. Create a new smart contract project using hardhat.

mkdir rasp_erc20_example_non_upgradeable_hardhat
cd rasp_erc20_example_non_upgradeable_hardhat
npm init
npx tsc --init
npm install --save-dev hardhat
npx hardhat init //select empty hardhat config and update the extenstion to .ts
mkdir contracts
mkdir scripts && echo > scripts/deploy.ts
touch contracts/Cube3ProtectedErc20.sol
  1. Install Cube3Protection and Openzeppelin interfaces using npm.

npm i @cube3/protection-solidity
  1. Now setup your CubeProtectedErc20.sol to have a simple unprotected erc20 token implementation:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Cube3ProtectedErc20 is ERC20 {
    constructor() ERC20("Cube3ProtectedErc20", "CTK") {
        _mint(msg.sender, 10000 * 10**decimals());
    }
}

At this point, we have setup a simple ERC-20 solidity project. Most common interactions with ERC-20 are either transfer or mint. The next section will protect these methods using CUBE3 RASP product.

Protecting transfer and mint methods

  1. Import Cube3Protection, Ownable and ERC20 interfaces:

import "@cube3/protection-solidity/src/Cube3Protection.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

Now run npx hardhat compile to make sure it compiles successfully.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@cube3/protection-solidity/src/Cube3Protection.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Cube3ProtectedErc20 is ERC20, Cube3Protection, Ownable {
    constructor(address _router)
    Cube3Protection(_router, msg.sender, true)
    ERC20("Cube3Token", "CTK")
    {
        _mint(msg.sender, 10000 * 10**decimals());
    }
}
  1. Add two new methods transferCube3Protected and mintCube3Protected:

function transferCube3Protected(
    address to,
    uint256 amount,
    bytes calldata cube3SecurePayload
) public cube3Protected(cube3SecurePayload) returns (bool) {
    super.transfer(to, amount);
    return true;
}

function mintCube3Protected(
    address to,
    uint256 amount,
    bytes calldata cube3SecurePayload
) public cube3Protected(cube3SecurePayload) {
    _mint(to, amount);
}

Looking at our function signatures we can see that both transferCube3Protected and mintCube3Protectedhave an additional parameter called cube3SecurePayload and uses cube3Protected modifier, which decodes cube3SecurePayload and reverts if it finds it malicious. We will see how to fetch the payload later.

  1. Since transfer is still a public function that can be accessed by anyone, we need to hide it from anyone else other than the admin (deployer) of the contract using onlyOwner modifier (we won't need to do the same with mint, because it is not public).

contract Cube3ProtectedErc20 is ERC20, Cube3Protection, Ownable {
    //<...> previous code
    
    //Hide public methods that we have previously protected
    function transfer(address to, uint256 amount) public override onlyOwner returns (bool) {
        return super.transfer(to, amount);
    }
}

And we are done protecting our contract!

Final implementation of CUBE3 Protected ERC-20 contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "@cube3/Cube3Protection.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract Cube3ProtectedErc20 is ERC20, Cube3Protection, Ownable {
    constructor(address _router)
    Cube3Protection(_router, msg.sender, true)
    ERC20("Cube3Token", "CTK")
    {
        _mint(msg.sender, 10000 * 10**decimals());
    }

    function transferCube3Protected(
        address to,
        uint256 amount,
        bytes calldata cube3SecurePayload
    ) public cube3Protected(cube3SecurePayload) returns (bool) {
        super.transfer(to, amount);
        return true;
    }

    function mintCube3Protected(
        address to,
        uint256 amount,
        bytes calldata cube3SecurePayload
    ) public cube3Protected(cube3SecurePayload) {
        _mint(to, amount);
    }

    //Hide public methods that we have previously protected
    function transfer(address to, uint256 amount) public override onlyOwner returns (bool) {
        return super.transfer(to, amount);
    }
}

2. Deployment

npm install --save-dev @nomicfoundation/hardhat-ethers ethers

Update hardhat.config.ts file:

import '@nomicfoundation/hardhat-ethers';

const sepoliaRPC = "sepolia-rpc-host";
const deployerPvyKet = "deployer-pvt-key";

module.exports = {
  solidity: "0.8.24",
  networks: {
    sepolia: {
      url: sepoliaRPC,
      accounts: [deployerPvyKet],
    },
  },
};

Add project files on tsconfig.ts:

"include": ["./src", "./scripts"],//only the "scripts" part.
"files": ["./hardhat.config.ts"]

Update scripts/deploy.ts file with the following:

import {ethers} from "hardhat";

async function main() {
  const [deployer] = await ethers.getSigners();

  console.log("Deploying contracts with the account:", deployer.address);

  const Cube3ProtectedErc20 = await ethers.getContractFactory("Cube3ProtectedErc20");
  const contract = await Cube3ProtectedErc20.deploy("ROUTER-ADDRESS");

  await contract.waitForDeployment();
  const address = await contract.getAddress()
  console.log(`Cube3ProtectedErc20 deployed to: ${address}`);
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Lastly, to deploy Cube3ProtectedErc20 contract, run the following in cmd:

npx hardhat run scripts/deploy.ts --network sepolia

Now since we have our contract protected and deployed, lastly we need to register it with CUBE3.

1. Registering with CUBE3 backend

You will be prompted with the Registration form, add the information:

  • Give your contract a name (for example Cube3ProtectedToken)

  • Add your deployed protected contract address

  • Select the chain you deployed your contract on

and press Add contract

Initially, your contract Integration Status will be Pending until we detect your contract on-chain, eventually it will process and say Not Registered. Click on your contract to expand it:

Click Reveal Registration Token and save it somewhere safe, we will use it in the next step.

2. Registering with CUBE3 Protocol (on-chain)

To register your contract with CUBE3 Protocol on-chain, you will need to call registerIntegrationWithCube3 function on the CUBE3 router contract.

  • Your deployed contract address

  • Registration token (the one you saved on the previous registration step)

  • Function selectors that you wish to protect (you will be able to disable protection later).

To generate function selectors simply run in your console:

cast sig "mintCube3Protected(address,uint256,bytes)"     //0xdfda26cd
cast sig "transferCube3Protected(address,uint256,bytes)" //0x11fb5122

Add necessary information and press Write. Make sure you do the transaction as the deployer of the contract (Admin) otherwise, it will revert.

Registration on both CUBE3 backend and CUBE3 Protocol is now done.

Go back to your RASP dashboard and you will see Integration Status of your contract to Registered and function protection On. If you expand your contract you will be able to see function selectors that are protected.

At this point, you are finished and ready to do your first CUBE3 Protected transaction!

Next, we will be doing CUBE3 protected transactions to our protected smart contract.

Interacting with your protected contract

Contents

Ideally, interacting with your protected smart contract would be in conjunction with the frontend and backend - the user presses a button (i.e. does a transaction to your protected smart contract) which initiates a backend call, frontend receives payload from it, and proceeds to submit it to the blockchain.

But for the sake of simplicity, let's just create a simple nodejs application that executes a transaction on our protected function.

1. Creating cube3_transaction_executer nodejs application

Let's start by creating a nodejs project using typescript.

mkdir cube3_transaction_executer
cd cube3_transaction_executer
npm init -y
npm install typescript --save-dev
npm install ts-node typescript --save-dev

echo '{
  "compilerOptions": {
    "target": "ES6",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "allowSyntheticDefaultImports": true,
    "resolveJsonModule": true,
    "esModuleInterop": true
  }
}' > tsconfig.json

create src/index.ts file where we will write our implementation. Initially, add this inside:

async function generatePayload() {
  //all of the code will go here
}

(async () => {
  await generatePayload();
})();

From your rasp_erc20_example_non_upgradeable_hardhat project directory, copy out/Cube3ProtectedErc20.sol/Cube3ProtectedErc20.json the file and paste it into cube3_transaction_executer/src the directory. We will need to access your protected contracts ABI.

2. Installing necessary packages

Install CUBE3 packages:

npm i @cube3/sdk-nodejs
npm i @cube3/sdk-ui
npm install ethers@6.9.0

3. Setup contract object

First, import all necessary functions, Cube3ProtectedErc20 and setup constants:

import {getBytes, ethers, Wallet, JsonRpcProvider } from "ethers";
import { constructCube3Validation } from "@cube3/sdk-ui";
import { cube3ValidateTransaction, cube3SaveTransaction } from "@cube3/sdk-nodejs";
import Cube3ProtectedErc20 from "./Cube3ProtectedErc20.json"

const validationUrl = "https://validation-api.cube3.ai/api/v1/transaction/validate"
const saveTransactionUrl = "https://validation-api.cube3.ai/api/v1/transaction"
const validationApiKey = "API-KEY"

const yourProtectedContractAddress = "0x<...>"

const payloadLength = 352;
const emptyByteArray: Uint8Array = new Uint8Array(payloadLength);

const mintTokensTo = "MINT-TOKENS-TO-ADDRESS";
const mintAmount = 1;

const yourWalletPrivateKey = ""

const rpcURL = ""

The code bellow goes into generatePayload() function.

const provider = new JsonRpcProvider(rpcURL);
const signer = new Wallet(yourWalletPrivateKey, provider)

const cube3TokenContract = new ethers.Contract(yourProtectedContractAddress, Cube3ProtectedErc20.abi, signer);

At this point, we have an ability to call our protected contract. Let's move to payload generation.

4. Payload generation

These are the steps:

  1. Pass it into constructCube3Validation function which will extract the necessary data for payload generation.

//1st step
const mintFuncData = await cube3TokenContract.transferBlockByParameter.populateTransaction(
    mintTokensTo,
    mintAmount,
    emptyByteArray,
    { //specify gas limit explicitly
      gasLimit: BigInt(400000)
    }
);

const mintTxData = await signer.populateTransaction(mintFuncData)
mintTxData.chainId = BigInt(11155111) //sepolia by default
mintTxData.value = BigInt(0) //we are not sending any eth

//2nd step
const cube3TransactionData = await constructCube3Validation(mintTxData, false, true);

//3rd step
const validation = await cube3ValidateTransaction(validationUrl, validationApiKey, cube3TransactionData, "")

//generated payload
console.log(validation.payload)

5. Interacting with your protected functions

In the previous step, we generated the payload for our protected mintCube3Protected function. Now all that is left is executing the transaction on blockchain.

if (validation.valid) {
  const mintFuncDataWithPayload = await cube3TokenContract.mintCube3Protected(
    mintTokensTo,
    mintAmount,
    validation.payload,
    {
      gasLimit: BigInt(400000),
    }
  );

  const mintTxReceipt = await signer.sendTransaction(mintFuncDataWithPayload);

  console.log("mintTxReceipt", mintTxReceipt)

  //lastly, save your transaction to view it on panorama dashboard
  await cube3SaveTransaction(saveTransactionUrl, validationApiKey, mintTxReceipt.hash, validation.cube3TxId)
}

From the project root directory, in cmd run:

 npx ts-node src/index.ts

Lastly, you can analyze any protected transaction on the Panorama dashboard:

Extend our Cube3ProtectedErc20 to implement Cube3Protection and setup the constructor (read more ).

To deploy, we are going to utilize plugin. Run the following cmd:

Head over to and click on Register your contract

For simplicity, to interact CUBE3 router contract - we suggest you to use etherscan. For example, for Sepolia - head over to and select registerIntegrationWithCube3. You will need to provide:

cube3_transaction_executer

For blockchain interactions, we will use :

To generate a payload, you need to do an HTTP request to CUBE3 validation service. For that, we will use . Let's begin.

For CUBE3 to generate a payload, it needs transaction data. For this, we will need to create a transaction object and pass it to .

For any interaction with a smart contract, we need to create a and objects.

Before we send an HTTP request to CUBE3 validation endpoint, we need to generate transaction data without actually executing the transaction. For this, we are going to utilize ethers to create the transaction object which stores information about the transaction.

Do an HTTP request to CUBE3 validation endpoint using cube3ValidateTransaction function, which will return us whether this transaction is safe and the payload along with other information (to learn more about what the validation service returns, read more ).

And the transaction receipt will appear. Copy the transaction hash and paste it into etherscan (for example sepolia) on which you should see a successful transaction

To learn more about the transaction dashboard, read .

here
ethers.js
https://panorama.cube3.ai/rasp-pro
https://sepolia.etherscan.io/address/0xB7a8c55aFe1B210ef37349574484CEdA2122D698#writeProxyContract
ethers.js
CUBE3 sdk
CUBE3 sdk
Wallet
Contract
populateTransaction
here
https://sepolia.etherscan.io/tx/<your-hash>
here
Writing ERC-20 contract
Protecting transfer and mint methods
Final implementation of CUBE3 Protected ERC-20 contract
Deployment
Registering contract with CUBE3
Registering with CUBE3 backend
Registering with CUBE3 protocol (on-chain)
Interacting with your protected contract
Creating
nodejs application
Installing necessary packages
Setup contract object
Payload generation
Interacting with your protected functions