Initialise TST MaxSupply on deployment (#37)

* Implement init maxSupply when deploying TST

* README and comments updates

* Move the maxSupply!=zero check to init function and add test

* Fix mintWithEth command in test/README.md

* Remove incorrect proxy deployment in TestStableTokenFactory

* Remove redundant post deploy check

* Update test/README with default token amount in ETH

* Update README section on Proxy address upgrade

* Add example env for TST commands

* Add prerequisites section to test/README
This commit is contained in:
Tanya S 2025-09-25 09:56:36 +02:00 committed by GitHub
parent c3ec4be6b4
commit a1d97fcad9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 91 additions and 12 deletions

View File

@ -11,13 +11,16 @@ contract DeployTokenWithProxy is BaseScript {
}
function deploy() public returns (ERC1967Proxy) {
// Read desired max supply from env or use default
uint256 defaultMaxSupply = vm.envOr({ name: "MAX_SUPPLY", defaultValue: uint256(1_000_000 * 10 ** 18) });
// Deploy the initial implementation
address implementation = address(new TestStableToken());
// Encode the initialize call
bytes memory data = abi.encodeCall(TestStableToken.initialize, (1_000_000 * 10 ** 18));
// Encode the initialize call (maxSupply)
bytes memory initData = abi.encodeCall(TestStableToken.initialize, (defaultMaxSupply));
// Deploy the proxy with initialization data
return new ERC1967Proxy(implementation, data);
return new ERC1967Proxy(implementation, initData);
}
}

28
test/.env.example.tst Normal file
View File

@ -0,0 +1,28 @@
# Example environment variables for TestStableToken commands in test/README.md
# Either provide a private key (`DEPLOYER_ACCOUNT_PRIVATE_KEY`) or a mnemonic (`TWELVE_WORD_MNEMONIC`).
# Deployer account (used as --from / ETH_FROM)
DEPLOYER_ACCOUNT_ADDRESS=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# Hex private key (prefixed with 0x) OR leave empty if you prefer to use mnemonic
DEPLOYER_ACCOUNT_PRIVATE_KEY=0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
# Alternatively, use a mnemonic instead of a private key
TWELVE_WORD_MNEMONIC="test test test test test test test test test test test junk"
# RPC URL for accessing testnet via HTTP.
# e.g. https://linea-sepolia.infura.io/v3/123aa110320f4aec179150fba1e1b1b1
RPC_URL=https://linea-sepolia.infura.io/v3/<key>
# Optional: override the default max supply (value is in wei; example below = 1_000_000 * 10**18)
# Uncomment and set to change the token cap used during initialize/upgrade
# MAX_SUPPLY=1000000000000000000000000
# Addresses used by various actions (leave commented if not applicable)
# Proxy contract (when calling upgrade, approve, mint, etc.)
# TOKEN_PROXY_ADDRESS=0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
# Example account to add to the minter allowlist
# ACCOUNT_ADDRESS=0xcccccccccccccccccccccccccccccccccccccccc
# Private key for a minter account (used when sending mint transactions)
# MINTER_ACCOUNT_PRIVATE_KEY=0xcccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc

View File

@ -19,12 +19,19 @@ token distribution while mimicking DAI's behaviour.
- **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)
## Prerequisites
- [Foundry](https://getfoundry.sh/) installed
- An Ethereum account with testnet ETH for deploying contracts and sending transactions
## Usage
Add environment variable `MAX_SUPPLY` to set the maximum supply of the token, otherwise it defaults to 1 million tokens.
### Deploy new TestStableToken with proxy contract
This script deploys both the proxy and the TestStableToken implementation, initializing the proxy to point to the new
implementation.
This script deploys both the proxy contract and the TestStableToken implementation contract, initializing the proxy to
point to the new implementation.
```bash
ETH_FROM=$DEPLOYER_ACCOUNT_ADDRESS forge script script/DeployTokenWithProxy.s.sol:DeployTokenWithProxy --rpc-url $RPC_URL --broadcast --private_key $DEPLOYER_ACCOUNT_PRIVATE_KEY
@ -36,22 +43,43 @@ or
MNEMONIC=$TWELVE_WORD_MNEMONIC forge script script/DeployTokenWithProxy.s.sol:DeployTokenWithProxy --rpc-url $RPC_URL --broadcast
```
### Deploy only TestStableToken contract implementation
### Deploy only TestStableToken implementation contract
This script deploys only the TestStableToken implementation, which can then be used to update the proxy contract to
point to this new implementation.
This script deploys only the TestStableToken implementation. See the upgrade instructions:
[Upgrade the proxy](#update-the-proxy-contract-to-point-to-the-new-implementation)
```bash
forge script test/TestStableToken.sol:TestStableTokenFactory --tc TestStableTokenFactory --rpc-url $RPC_URL --private-key $DEPLOYER_ACCOUNT_PRIVATE_KEY --broadcast
ETH_FROM=$DEPLOYER_ACCOUNT_ADDRESS forge script test/TestStableToken.sol:TestStableTokenFactory --tc TestStableTokenFactory --rpc-url $RPC_URL --private-key $DEPLOYER_ACCOUNT_PRIVATE_KEY --broadcast
```
### Update the proxy contract to point to the new implementation
#### 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)`.
```bash
# Upgrade the proxy to a new implementation
cast send $TOKEN_PROXY_ADDRESS "upgradeTo(address)" $NEW_IMPLEMENTATION_ADDRESS --rpc-url $RPC_URL --private-key $DEPLOYER_ACCOUNT_PRIVATE_KEY
```
#### 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.
### Add account to the allowlist to enable minting
```bash
@ -68,8 +96,10 @@ cast send $TOKEN_PROXY_ADDRESS "mint(address,uint256)" <TO_ADDRESS> <AMOUNT> --r
#### Option 2: Public minting by burning ETH (no privileges required)
The total tokens minted is determined by the amount of ETH sent with the transaction.
```bash
cast send $TOKEN_PROXY_ADDRESS "mintWithETH(address,uint256)" <TO_ACCOUNT> <AMOUNT> --value <ETH_AMOUNT> --rpc-url $RPC_URL --private-key $MINTING_ACCOUNT_PRIVATE_KEY --from $MINTING_ACCOUNT_ADDRESS
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
```
**Note**: The `mintWithETH` function is public and can be called by anyone. It requires sending ETH with the transaction

View File

@ -8,12 +8,14 @@ import { ERC20PermitUpgradeable } from
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
error AccountNotMinter();
error AccountAlreadyMinter();
error AccountNotInMinterList();
error InsufficientETH();
error ExceedsMaxSupply();
error InvalidMaxSupply(uint256 supplied);
contract TestStableToken is
Initializable,
@ -44,6 +46,7 @@ contract TestStableToken is
__ERC20Permit_init("TestStableToken");
__Ownable_init();
__UUPSUpgradeable_init();
if (_maxSupply == 0) revert InvalidMaxSupply(_maxSupply);
maxSupply = _maxSupply;
}

View File

@ -8,7 +8,8 @@ import {
AccountAlreadyMinter,
AccountNotInMinterList,
InsufficientETH,
ExceedsMaxSupply
ExceedsMaxSupply,
InvalidMaxSupply
} from "./TestStableToken.sol";
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
import { DeployTokenWithProxy } from "../script/DeployTokenWithProxy.s.sol";
@ -336,4 +337,18 @@ contract TestStableTokenTest is Test {
vm.expectRevert("Ownable: caller is not the owner");
token.setMaxSupply(newMaxSupply);
}
function test__InitializeZeroReverts() external {
// Deploy implementation directly
TestStableToken implementation = new TestStableToken();
// Build initializer calldata with zero
bytes memory initData = abi.encodeCall(TestStableToken.initialize, (uint256(0)));
// Expect the InvalidMaxSupply reversion including the supplied value
vm.expectRevert(abi.encodeWithSelector(InvalidMaxSupply.selector, uint256(0)));
// Attempt to deploy proxy with initData - should revert
new ERC1967Proxy(address(implementation), initData);
}
}