2025-08-26 17:34:32 +02:00
|
|
|
# TestStableToken
|
|
|
|
|
|
|
|
|
|
The waku-rlnv2-contract [spec](https://github.com/waku-org/specs/blob/master/standards/core/rln-contract.md) defines
|
|
|
|
|
that DAI is to be used to pay for membership registration, with the end-goal being to deploy the contract on mainnet
|
|
|
|
|
using an existing stable DAI token.
|
|
|
|
|
|
|
|
|
|
Before this, we need to perform extensive testing on testnet and local environments (such as
|
|
|
|
|
[waku-simulator](https://github.com/waku-org/waku-simulator)). During initial testing, we discovered the need to manage
|
|
|
|
|
token minting in testnet environments to limit membership registrations and enable controlled testing of the contract.
|
|
|
|
|
|
|
|
|
|
TestStableToken is our custom token implementation designed specifically for testing environments, providing controlled
|
|
|
|
|
token distribution while mimicking DAI's behaviour.
|
|
|
|
|
|
|
|
|
|
## Requirements
|
|
|
|
|
|
|
|
|
|
- **Controlled minting**: Manage token minting through an allowlist of approved accounts, controlled by the token
|
|
|
|
|
contract owner
|
|
|
|
|
- **ETH burning mechanism**: Burn ETH when minting tokens to create economic cost (WIP)
|
|
|
|
|
- **Proxy architecture**: Use a proxy contract to minimize updates required when the token address changes across other
|
|
|
|
|
components (e.g., nwaku-compose repo or dogfooding instructions)
|
|
|
|
|
|
2025-09-25 09:56:36 +02:00
|
|
|
## Prerequisites
|
|
|
|
|
|
|
|
|
|
- [Foundry](https://getfoundry.sh/) installed
|
|
|
|
|
- An Ethereum account with testnet ETH for deploying contracts and sending transactions
|
|
|
|
|
|
2025-08-26 17:34:32 +02:00
|
|
|
## Usage
|
|
|
|
|
|
2025-09-25 09:56:36 +02:00
|
|
|
Add environment variable `MAX_SUPPLY` to set the maximum supply of the token, otherwise it defaults to 1 million tokens.
|
|
|
|
|
|
2025-08-26 17:34:32 +02:00
|
|
|
### Deploy new TestStableToken with proxy contract
|
|
|
|
|
|
2025-09-25 09:56:36 +02:00
|
|
|
This script deploys both the proxy contract and the TestStableToken implementation contract, initializing the proxy to
|
|
|
|
|
point to the new implementation.
|
2025-08-26 17:34:32 +02:00
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
ETH_FROM=$DEPLOYER_ACCOUNT_ADDRESS forge script script/DeployTokenWithProxy.s.sol:DeployTokenWithProxy --rpc-url $RPC_URL --broadcast --private_key $DEPLOYER_ACCOUNT_PRIVATE_KEY
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
or
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
MNEMONIC=$TWELVE_WORD_MNEMONIC forge script script/DeployTokenWithProxy.s.sol:DeployTokenWithProxy --rpc-url $RPC_URL --broadcast
|
|
|
|
|
```
|
|
|
|
|
|
2025-09-25 09:56:36 +02:00
|
|
|
### Deploy only TestStableToken implementation contract
|
2025-08-26 17:34:32 +02:00
|
|
|
|
2025-09-25 09:56:36 +02:00
|
|
|
This script deploys only the TestStableToken implementation. See the upgrade instructions:
|
|
|
|
|
[Upgrade the proxy](#update-the-proxy-contract-to-point-to-the-new-implementation)
|
2025-08-26 17:34:32 +02:00
|
|
|
|
|
|
|
|
```bash
|
2025-09-25 09:56:36 +02:00
|
|
|
ETH_FROM=$DEPLOYER_ACCOUNT_ADDRESS forge script test/TestStableToken.sol:TestStableTokenFactory --tc TestStableTokenFactory --rpc-url $RPC_URL --private-key $DEPLOYER_ACCOUNT_PRIVATE_KEY --broadcast
|
2025-08-26 17:34:32 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Update the proxy contract to point to the new implementation
|
|
|
|
|
|
2025-09-25 09:56:36 +02:00
|
|
|
#### Option 1: Update proxy implementation address only (recommended when the proxy is already initialized)
|
|
|
|
|
|
|
|
|
|
When the proxy is already initialized and the `maxSupply` is set, you can simply update the implementation address using
|
|
|
|
|
`upgradeTo(address)`.
|
|
|
|
|
|
2025-08-26 17:34:32 +02:00
|
|
|
```bash
|
|
|
|
|
cast send $TOKEN_PROXY_ADDRESS "upgradeTo(address)" $NEW_IMPLEMENTATION_ADDRESS --rpc-url $RPC_URL --private-key $DEPLOYER_ACCOUNT_PRIVATE_KEY
|
|
|
|
|
```
|
|
|
|
|
|
2025-09-25 09:56:36 +02:00
|
|
|
#### Option 2: Update proxy implementation address and initialize cap
|
|
|
|
|
|
|
|
|
|
When upgrading a UUPS/ERC1967 proxy you should perform the upgrade and initialization in the same transaction to avoid
|
|
|
|
|
leaving the proxy in an uninitialized state. Use `upgradeToAndCall(address,bytes)` with the initializer calldata.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Encode the initializer calldata (example: set MAX_SUPPLY to 1_000_000 ETH = 1_000_000 * 10**18)
|
|
|
|
|
DATA=$(cast abi-encode "initialize(uint256)" 1000000000000000000000000)
|
|
|
|
|
|
|
|
|
|
# Perform upgrade and call initializer atomically
|
|
|
|
|
cast send $TOKEN_PROXY_ADDRESS "upgradeToAndCall(address,bytes)" $NEW_IMPLEMENTATION_ADDRESS $DATA --rpc-url $RPC_URL --private-key $DEPLOYER_ACCOUNT_PRIVATE_KEY
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
If you must call `upgradeTo` separately (not recommended), follow up immediately with an `initialize(...)` call in the
|
|
|
|
|
same transaction or as the next transaction from the owner/multisig. However, prefer `upgradeToAndCall` to eliminate the
|
|
|
|
|
time window where the proxy points to a new implementation but its storage (e.g., `maxSupply`) is uninitialized.
|
|
|
|
|
|
2025-08-26 17:34:32 +02:00
|
|
|
### Add account to the allowlist to enable minting
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cast send $TOKEN_PROXY_ADDRESS "addMinter(address)" $ACCOUNT_ADDRESS --rpc-url $RPC_URL --private-key $DEPLOYER_ACCOUNT_PRIVATE_KEY
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Mint tokens to the account
|
|
|
|
|
|
2025-09-11 10:45:43 +02:00
|
|
|
#### Option 1: Restricted minting (requires minter privileges)
|
|
|
|
|
|
2025-08-26 17:34:32 +02:00
|
|
|
```bash
|
|
|
|
|
cast send $TOKEN_PROXY_ADDRESS "mint(address,uint256)" <TO_ADDRESS> <AMOUNT> --rpc-url $RPC_URL --private-key $MINTER_ACCOUNT_PRIVATE_KEY
|
|
|
|
|
```
|
|
|
|
|
|
2025-09-11 10:45:43 +02:00
|
|
|
#### Option 2: Public minting by burning ETH (no privileges required)
|
|
|
|
|
|
2025-10-10 08:12:59 +02:00
|
|
|
The total tokens minted is determined by the amount of ETH sent with the transaction. There is a lower limit to the
|
|
|
|
|
amount that can be minted in a single transaction to prevent spam.
|
2025-09-25 09:56:36 +02:00
|
|
|
|
2025-09-11 10:45:43 +02:00
|
|
|
```bash
|
2025-09-25 09:56:36 +02:00
|
|
|
cast send $TOKEN_PROXY_ADDRESS "mintWithETH(address)" <TO_ACCOUNT> --value <ETH_AMOUNT> --rpc-url $RPC_URL --private-key $MINTING_ACCOUNT_PRIVATE_KEY --from $MINTING_ACCOUNT_ADDRESS
|
2025-09-11 10:45:43 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**Note**: The `mintWithETH` function is public and can be called by anyone. It requires sending ETH with the transaction
|
|
|
|
|
(using `--value`), which gets burned (sent to address(0)) as an economic cost for minting tokens. This provides a
|
|
|
|
|
permissionless way to obtain tokens for testing without requiring minter privileges.
|
|
|
|
|
|
2025-08-26 17:34:32 +02:00
|
|
|
### Approve the token for the waku-rlnv2-contract to use
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cast send $TOKEN_PROXY_ADDRESS "approve(address,uint256)" $TOKEN_SPENDER_ADDRESS <AMOUNT> --rpc-url $RPC_URL --private-key $PRIVATE_KEY
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Remove the account from the allowlist to prevent further minting
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cast send $TOKEN_PROXY_ADDRESS "removeMinter(address)" $ACCOUNT_ADDRESS --rpc-url $RPC_URL --private-key $DEPLOYER_ACCOUNT_PRIVATE_KEY
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Query token information
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Check if an account is a minter
|
|
|
|
|
cast call $TOKEN_PROXY_ADDRESS "isMinter(address)" $ACCOUNT_ADDRESS --rpc-url $RPC_URL
|
|
|
|
|
|
|
|
|
|
# Check token balance
|
|
|
|
|
cast call $TOKEN_PROXY_ADDRESS "balanceOf(address)" $ACCOUNT_ADDRESS --rpc-url $RPC_URL
|
|
|
|
|
|
|
|
|
|
# Check token allowance
|
|
|
|
|
cast call $TOKEN_PROXY_ADDRESS "allowance(address,address)" $TOKEN_OWNER_ADDRESS $TOKEN_SPENDER_ADDRESS --rpc-url $RPC_URL
|
|
|
|
|
```
|