Caution
While this upgradeable token smart contract is built using audited OZ libraries,
this particular implementation has not yet underwent any security audits. Use at your own risk.
What is needed is an ERC20 implementation which
- Has centralized minting,
- Is pausable,
- Is upgradeable.
For pp.1-2 OZ’s ERC20Upgradeable should be used with corresponding extensions.
For p.3 (upgradeability) the following options are available:
- TransparentUpgradeableProxy: A proxy with a built in admin and upgrade interface
- UUPSUpgradeable: An upgradeability mechanism to be included in the implementation contract.
(source)
We prefer UUPSUpgradeable proxy, because it allows to eventually make the implementation contract non-upgradeable, therefore making its final version immutable, which might be preferable for exchanges and token holders as it implies less trust and removes possible security breaches (like e.g. updating token contract to something working not as agreed or breaking its logic partially or overall).
Laid out at this Notion page.
See SPEC.md.
Our token implementation is based on well-tested and audited industry-wide standard libraries and plugins:
This project is built with the Foundry framework.
If changed some contract, run this first:
forge fmt && forge clean && forge build
Then run tests:
forge test
Expand me
Spin out a default Anvil node:
anvil -p 9545
Load environment variables and run the deployment script:
source .env.anvil
forge script script/00_Deploy.s.sol --rpc-url $ANVIL_RPC_URL --broadcast -i 1
It will ask you to enter the private key. As we're using Anvil's default account (0)
as the deployer (specified in the .env.anvil
), use its (!well-known!) key here (can be found in Anvil logs).
Important
You need to setup environment first, see .env.sepolia.example
Expand me
Spin out an Anvil fork of Sepolia network:
source .env.sepolia
anvil -f $SEPOLIA_RPC_URL -p 9545
Deploy:
source .env.sepolia
forge script script/00_Deploy.s.sol --rpc-url $ANVIL_RPC_URL --broadcast -i 1
Make sure to provide the private key of the DEPLOYER
account upon script's interactive prompt.
Once steps described above taken and succeed, deploy to Sepolia with:
forge script script/00_Deploy.s.sol --rpc-url $SEPOLIA_RPC_URL --broadcast -i 1
Note
You can also use hardware wallet for signing the transaction. E.g. for using with Ledger run:
forge script script/00_Deploy.s.sol --rpc-url $ANVIL_RPC_URL --broadcast -l
Figure out solc
version used to compile the contracts:
forge inspect src/AnlogTokenV1.sol:AnlogTokenV1 metadata | jq .compiler.version
To verify proxy contract run:
forge v --verifier etherscan --compiler-version=<solc_version> \
--rpc-url=$SEPOLIA_RPC_URL <proxy_address> \
./lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/proxy/ERC1967/ERC1967Proxy.sol:ERC1967Proxy
To verify implementation contract run:
forge v --verifier etherscan --compiler-version=<solc_version> \
--rpc-url $SEPOLIA_RPC_URL <implementation_addresss> \
src/AnlogTokenV1.sol:AnlogTokenV1
Note
Commands below are given for the setting when you upgrade from V0
to V1Upgrade
. See Upgrade.V0.V1.t.sol
for the reference.
Let say you have deployed proxy and V0
implementation contract for it.
To upgrade the implementation contract to V1Upgrade
:
-
Deploy
V1Upgrade
:source .env.sepolia forge create --constructor-args=0x,0x,0x,0x \ --rpc-url $SEPOLIA_RPC_URL \ --from $DEPLOYER \ test/mock/AnlogTokenV1Upgrade.sol:AnlogTokenV1Upgrade -i --broadcast
NOTE that we provided
0x,0x,0x,0x
to the constructor args. That is because we don't care on the values of the implementation contract storage. The proxy storage will be updated in the followinginitialize()
call right after the upgrade. -
Prepare
calldata
forV1Upgrade
initializer:source .env.sepolia cast cd "initialize(address,address,address,address)()" $MINTER $UPGRADER $PAUSER $UNPAUSER
You should get hex-encoded
calldata
as the result. -
Dispatch the call to
upgradeToAndCall(address,bytes)
, providing the following args:- address: current (
V0
) implementation contract address; - bytes:
calldata
for theV1Upgrade
implementation contract initializer (the one you've got on the previous step).
- address: current (