mirror of
https://github.com/logos-messaging/logos-messaging-rlnv2-contract.git
synced 2026-01-03 22:43:12 +00:00
Use proxy for token contract (#30)
* Add proxy contract for TST * Fix token proxy update function to use provided new TST address * Transfer token proxy contract ownership to deployer * Add Token Proxy Contract Owner as init input * Add UUPSUPgradeable to TST * Formatting * fix import format * Add README to explain TST usage * Linting fix * Check TST test transfer return val * Add descriptions in README for TST usage * Fix linting * Use TST token deployer in test conrtact, update test README * USe assertTrue in TST test
This commit is contained in:
parent
900d4f95e0
commit
b4508dd0d4
@ -9,7 +9,7 @@ import { LazyIMT } from "@zk-kit/imt.sol/LazyIMT.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import { DevOpsTools } from "lib/foundry-devops/src/DevOpsTools.sol";
|
||||
import { BaseScript } from "./Base.s.sol";
|
||||
import "forge-std/console.sol";
|
||||
import { console } from "forge-std/console.sol";
|
||||
|
||||
contract DeployPriceCalculator is BaseScript {
|
||||
function run() public broadcast returns (address) {
|
||||
|
||||
23
script/DeployTokenWithProxy.s.sol
Normal file
23
script/DeployTokenWithProxy.s.sol
Normal file
@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity >=0.8.19 <0.9.0;
|
||||
|
||||
import { BaseScript } from "./Base.s.sol";
|
||||
import { TestStableToken } from "../test/TestStableToken.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
|
||||
contract DeployTokenWithProxy is BaseScript {
|
||||
function run() public broadcast returns (address) {
|
||||
return address(deploy());
|
||||
}
|
||||
|
||||
function deploy() public returns (ERC1967Proxy) {
|
||||
// Deploy the initial implementation
|
||||
address implementation = address(new TestStableToken());
|
||||
|
||||
// Encode the initialize call
|
||||
bytes memory data = abi.encodeCall(TestStableToken.initialize, ());
|
||||
|
||||
// Deploy the proxy with initialization data
|
||||
return new ERC1967Proxy(implementation, data);
|
||||
}
|
||||
}
|
||||
90
test/README.md
Normal file
90
test/README.md
Normal file
@ -0,0 +1,90 @@
|
||||
# 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)
|
||||
|
||||
## Usage
|
||||
|
||||
### 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.
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Deploy only TestStableToken contract implementation
|
||||
|
||||
This script deploys only the TestStableToken implementation, which can then be used to update the proxy contract to
|
||||
point to this new implementation.
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### 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
|
||||
|
||||
```bash
|
||||
cast send $TOKEN_PROXY_ADDRESS "mint(address,uint256)" <TO_ADDRESS> <AMOUNT> --rpc-url $RPC_URL --private-key $MINTER_ACCOUNT_PRIVATE_KEY
|
||||
```
|
||||
|
||||
### 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
|
||||
```
|
||||
@ -2,15 +2,24 @@
|
||||
pragma solidity >=0.8.19 <0.9.0;
|
||||
|
||||
import { BaseScript } from "../script/Base.s.sol";
|
||||
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
|
||||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
|
||||
import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
|
||||
import { ERC20PermitUpgradeable } from
|
||||
"@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
|
||||
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";
|
||||
|
||||
error AccountNotMinter();
|
||||
error AccountAlreadyMinter();
|
||||
error AccountNotInMinterList();
|
||||
|
||||
contract TestStableToken is ERC20, ERC20Permit, Ownable {
|
||||
contract TestStableToken is
|
||||
Initializable,
|
||||
ERC20Upgradeable,
|
||||
ERC20PermitUpgradeable,
|
||||
OwnableUpgradeable,
|
||||
UUPSUpgradeable
|
||||
{
|
||||
mapping(address => bool) public isMinter;
|
||||
|
||||
event MinterAdded(address indexed account);
|
||||
@ -21,7 +30,18 @@ contract TestStableToken is ERC20, ERC20Permit, Ownable {
|
||||
_;
|
||||
}
|
||||
|
||||
constructor() ERC20("TestStableToken", "TST") ERC20Permit("TestStableToken") Ownable() { }
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
function initialize() public initializer {
|
||||
__ERC20_init("TestStableToken", "TST");
|
||||
__ERC20Permit_init("TestStableToken");
|
||||
__Ownable_init();
|
||||
__UUPSUpgradeable_init();
|
||||
}
|
||||
|
||||
function _authorizeUpgrade(address newImplementation) internal override onlyOwner { }
|
||||
|
||||
function addMinter(address account) external onlyOwner {
|
||||
if (isMinter[account]) revert AccountAlreadyMinter();
|
||||
|
||||
@ -3,17 +3,26 @@ pragma solidity >=0.8.19 <0.9.0;
|
||||
|
||||
import { Test } from "forge-std/Test.sol";
|
||||
import { TestStableToken, AccountNotMinter, AccountAlreadyMinter, AccountNotInMinterList } from "./TestStableToken.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import { DeployTokenWithProxy } from "../script/DeployTokenWithProxy.s.sol";
|
||||
|
||||
contract TestStableTokenTest is Test {
|
||||
TestStableToken internal token;
|
||||
DeployTokenWithProxy internal deployer;
|
||||
address internal owner;
|
||||
address internal user1;
|
||||
address internal user2;
|
||||
address internal nonMinter;
|
||||
|
||||
function setUp() public {
|
||||
token = new TestStableToken();
|
||||
owner = address(this);
|
||||
// Deploy using the deployment script
|
||||
deployer = new DeployTokenWithProxy();
|
||||
ERC1967Proxy proxy = deployer.deploy();
|
||||
|
||||
// Wrap proxy in TestStableToken interface
|
||||
token = TestStableToken(address(proxy));
|
||||
|
||||
owner = address(deployer);
|
||||
user1 = vm.addr(1);
|
||||
user2 = vm.addr(2);
|
||||
nonMinter = vm.addr(3);
|
||||
@ -22,15 +31,18 @@ contract TestStableTokenTest is Test {
|
||||
function test__OwnerCanAddMinterRole() external {
|
||||
assertFalse(token.isMinter(user1));
|
||||
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
|
||||
assertTrue(token.isMinter(user1));
|
||||
}
|
||||
|
||||
function test__OwnerCanRemoveMinterRole() external {
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
assertTrue(token.isMinter(user1));
|
||||
|
||||
vm.prank(owner);
|
||||
token.removeMinter(user1);
|
||||
|
||||
assertFalse(token.isMinter(user1));
|
||||
@ -39,6 +51,7 @@ contract TestStableTokenTest is Test {
|
||||
function test__OwnerCanMintWithoutMinterRole() external {
|
||||
uint256 mintAmount = 1000 ether;
|
||||
|
||||
vm.prank(owner);
|
||||
token.mint(user1, mintAmount);
|
||||
|
||||
assertEq(token.balanceOf(user1), mintAmount);
|
||||
@ -51,6 +64,7 @@ contract TestStableTokenTest is Test {
|
||||
}
|
||||
|
||||
function test__NonOwnerCannotRemoveMinterRole() external {
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
|
||||
vm.prank(user1);
|
||||
@ -59,19 +73,23 @@ contract TestStableTokenTest is Test {
|
||||
}
|
||||
|
||||
function test__CannotAddAlreadyMinterRole() external {
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
|
||||
vm.expectRevert(abi.encodeWithSelector(AccountAlreadyMinter.selector));
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
}
|
||||
|
||||
function test__CannotRemoveNonMinterRole() external {
|
||||
vm.expectRevert(abi.encodeWithSelector(AccountNotInMinterList.selector));
|
||||
vm.prank(owner);
|
||||
token.removeMinter(user1);
|
||||
}
|
||||
|
||||
function test__MinterRoleCanMint() external {
|
||||
uint256 mintAmount = 1000 ether;
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
|
||||
vm.prank(user1);
|
||||
@ -90,7 +108,9 @@ contract TestStableTokenTest is Test {
|
||||
|
||||
function test__MultipleMinterRolesCanMint() external {
|
||||
uint256 mintAmount = 500 ether;
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
vm.prank(owner);
|
||||
token.addMinter(user2);
|
||||
|
||||
vm.prank(user1);
|
||||
@ -104,7 +124,9 @@ contract TestStableTokenTest is Test {
|
||||
|
||||
function test__RemovedMinterRoleCannotMint() external {
|
||||
uint256 mintAmount = 1000 ether;
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
vm.prank(owner);
|
||||
token.removeMinter(user1);
|
||||
|
||||
vm.prank(user1);
|
||||
@ -116,7 +138,8 @@ contract TestStableTokenTest is Test {
|
||||
uint256 mintAmount = 500 ether;
|
||||
|
||||
// Owner is not in minter role but should still be able to mint
|
||||
assertFalse(token.isMinter(address(this)));
|
||||
assertFalse(token.isMinter(owner));
|
||||
vm.prank(owner);
|
||||
token.mint(user1, mintAmount);
|
||||
assertEq(token.balanceOf(user1), mintAmount);
|
||||
}
|
||||
@ -125,20 +148,24 @@ contract TestStableTokenTest is Test {
|
||||
assertFalse(token.isMinter(user1));
|
||||
assertFalse(token.isMinter(user2));
|
||||
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
assertTrue(token.isMinter(user1));
|
||||
assertFalse(token.isMinter(user2));
|
||||
|
||||
vm.prank(owner);
|
||||
token.addMinter(user2);
|
||||
assertTrue(token.isMinter(user1));
|
||||
assertTrue(token.isMinter(user2));
|
||||
|
||||
vm.prank(owner);
|
||||
token.removeMinter(user1);
|
||||
assertFalse(token.isMinter(user1));
|
||||
assertTrue(token.isMinter(user2));
|
||||
}
|
||||
|
||||
function test__ERC20BasicFunctionality() external {
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
uint256 mintAmount = 1000 ether;
|
||||
|
||||
@ -149,7 +176,7 @@ contract TestStableTokenTest is Test {
|
||||
assertEq(token.totalSupply(), mintAmount);
|
||||
|
||||
vm.prank(user2);
|
||||
token.transfer(owner, 200 ether);
|
||||
assertTrue(token.transfer(owner, 200 ether));
|
||||
|
||||
assertEq(token.balanceOf(user2), 800 ether);
|
||||
assertEq(token.balanceOf(owner), 200 ether);
|
||||
@ -159,15 +186,18 @@ contract TestStableTokenTest is Test {
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit MinterAdded(user1);
|
||||
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
}
|
||||
|
||||
function test__MinterRemovedEventEmitted() external {
|
||||
vm.prank(owner);
|
||||
token.addMinter(user1);
|
||||
|
||||
vm.expectEmit(true, true, false, false);
|
||||
emit MinterRemoved(user1);
|
||||
|
||||
vm.prank(owner);
|
||||
token.removeMinter(user1);
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ pragma solidity >=0.8.19 <0.9.0;
|
||||
|
||||
import { Test } from "forge-std/Test.sol";
|
||||
import { DeployPriceCalculator, DeployWakuRlnV2, DeployProxy } from "../script/Deploy.s.sol";
|
||||
import { DeployTokenWithProxy } from "../script/DeployTokenWithProxy.s.sol";
|
||||
import "../src/WakuRlnV2.sol"; // solhint-disable-line
|
||||
import "../src/Membership.sol"; // solhint-disable-line
|
||||
import { IPriceCalculator } from "../src/IPriceCalculator.sol";
|
||||
@ -17,21 +18,27 @@ import "forge-std/console.sol";
|
||||
contract WakuRlnV2Test is Test {
|
||||
WakuRlnV2 internal w;
|
||||
TestStableToken internal token;
|
||||
DeployTokenWithProxy internal tokenDeployer;
|
||||
|
||||
address internal deployer;
|
||||
|
||||
uint256[] internal noIdCommitmentsToErase = new uint256[](0);
|
||||
|
||||
function setUp() public virtual {
|
||||
token = new TestStableToken();
|
||||
// Deploy TestStableToken through proxy using deployment script
|
||||
tokenDeployer = new DeployTokenWithProxy();
|
||||
ERC1967Proxy tokenProxy = tokenDeployer.deploy();
|
||||
token = TestStableToken(address(tokenProxy));
|
||||
|
||||
IPriceCalculator priceCalculator = (new DeployPriceCalculator()).deploy(address(token));
|
||||
WakuRlnV2 wakuRlnV2 = (new DeployWakuRlnV2()).deploy();
|
||||
ERC1967Proxy proxy = (new DeployProxy()).deploy(address(priceCalculator), address(wakuRlnV2));
|
||||
|
||||
w = WakuRlnV2(address(proxy));
|
||||
|
||||
// TestStableTokening a large number of tokens to not have to worry about
|
||||
// Minting a large number of tokens to not have to worry about
|
||||
// Not having enough balance
|
||||
vm.prank(address(tokenDeployer));
|
||||
token.mint(address(this), 100_000_000 ether);
|
||||
}
|
||||
|
||||
@ -634,6 +641,7 @@ contract WakuRlnV2Test is Test {
|
||||
vm.prank(priceCalculator.owner());
|
||||
priceCalculator.setTokenAndPrice(address(token), 5 wei);
|
||||
(, uint256 price) = w.priceCalculator().calculate(membershipRateLimit);
|
||||
vm.prank(address(tokenDeployer));
|
||||
token.mint(address(this), price);
|
||||
vm.assume(
|
||||
w.minMembershipRateLimit() <= membershipRateLimit && membershipRateLimit <= w.maxMembershipRateLimit()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user