ERC-20 Upgradeable (Foundry)
Contents
Writing ERC-20 contract
Create a new smart contract project using foundry.
Rename src/Counter.sol
to src/Cube3ProtectedErc20UUPS.sol
and for now, just delete test/Counter.t.sol
.
Install
Cube3Protection
interface using Foundry
Open your
foundry.toml
file and addauto_detect_remappings = false
- if enabled, Foundry will automatically try auto-detect remappings by scanning thelibs
folder(s). Your foundry.toml file should look like this:
Setup
remappings.txt
to make sure the compiler points to the CUBE3 library. Since we installedcube3-protection-solidity
usingFoundry
, it will be saved inlib
. Update yourremappings.txt
contents to have the following:
Now setup your
Cube3ProtectedErc20UUPS.sol
to be an empty contract:
Most common interactions with ERC-20 are either transfer
or mint
. In the next section, we are going to protect these methods using the CUBE3 RASP product.
Protecting transfer
and mint
methods
transfer
and mint
methodsImport
Cube3ProtectionUpgradeable
,UUPSUpgradeable
andERC20Upgradeable
interfaces:
Now run forge build
to make sure it compiles successfully.
Extend our
Cube3ProtectedErc20UUPS
to implementUUPS
interfaces and setup theinitialize
function (read more here):
Add two new methods
transferCube3Protected
andmintCube3Protected
:
Looking at our function signatures we can see that both transferCube3Protected
and mintCube3Protected
have 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.
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 usingonlyOwner
modifier (we won't need to do the same withmint
, because it is not public).
And we are done protecting our contract!
Final implementation of CUBE3 Protected Upgradeable ERC-20 contract
2. Deployment
Deployment of upgradeable contracts takes two steps:
Deploy implementation i.e.
Cube3ProtectedErc20UUPS
contractDeploy
ERC1967Proxy
which will point to our implementation contract
To deploy Cube3ProtectedErc20UUPS
contract, run the following in cmd:
(Optional) verify implementation contract:
Next, let's deploy ERC1967Proxy
which will point to our implementation. For that, we will need to write a foundry script. Create a new file script/deployProxy.s.sol
and paste the following:
Now to run this script, enter the following into cmd:
In the console you should see your ERC1967Proxy
address:
Now since we have our contract protected and deployed, lastly we need to register it with CUBE3.
1. Registering with CUBE3 backend
Head over to https://panorama.cube3.ai/rasp-pro and click on Register your contract
You will be prompted with the Registration form, add the information:
Give your contract a name (for example
Cube3ProtectedToken
)Add your deployed
ERC1967Proxy
contract addressSelect the chain you deployed your contract on
and press Add contract
Initially, your contractIntegration 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, 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.
For simplicity, to interact CUBE3 router contract - we suggest you to use etherscan. For example, for Sepolia - head over to https://sepolia.etherscan.io/address/0xB7a8c55aFe1B210ef37349574484CEdA2122D698#writeProxyContractand select registerIntegrationWithCube3
. You will need to provide:
Your deployed contract address (proxy)
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:
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.
Next, we will be doing CUBE3 protected transactions to our protected upgradeable smart contract.
Interacting with your protected contract
Contents
Creating
cube3_transaction_executer
nodejs application
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, the 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
cube3_transaction_executer
nodejs applicationLet's start by creating a nodejs project using typescript.
create src/index.ts
file where we will write our implementation. Initially, add this inside:
From your rasp_erc20_example_upgradeable
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:
For blockchain interactions, we will use ethers.js
:
3. Setup contract object
To generate a payload, you need to do an HTTP request to the CUBE3 validation service. For that, we will use CUBE3 sdk. Let's begin.
First, import all necessary functions, Cube3ProtectedErc20
and setup constants:
For CUBE3 to generate a payload, it needs transaction data. For this, we will need to create a transaction object and pass it to CUBE3 sdk.
The code bellow goes into generatePayload()
function.
For any interaction with a smart contract, we need to create a Wallet and Contract objects.
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:
Before we send an HTTP request to the CUBE3 validation endpoint, we need to generate transaction data without actually executing the transaction. For this, we are going to utilize ethers populateTransaction to create the transaction object which stores information about the transaction.
Pass it into
constructCube3Validation
function which will extract the necessary data for payload generation.Do an HTTP request to the 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 here).
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.
From the project root directory, in cmd run:
And the transaction receipt will appear. Copy the transaction hash and paste it into etherscan (for example sepolia) https://sepolia.etherscan.io/tx/<your-hash> on which you should see a successful transaction
Lastly, you can analyze any protected transaction on the Panorama dashboard:
To learn more about the transaction dashboard, read here.
Last updated