DEPRECATED REPO - PLEASE VISIT https://wdoge.tech
The Dogecoin-Ethereum bridge requires a token to represent doges in the Ethereum network. This repository contains a token contract that fulfills this goal.
The token is built on top of two contracts deployed to two separate accounts:
- The proxy. This contract allows upgrading the token implementation.
- The token implementation. This contract implements the ERC20 interface and a few more functions for minting, burning and transferring the ownership of the contract.
There are three user roles in the system:
- Proxy admin.
- Can execute upgrades.
- Cannot interact with the token functionality. E.g. the proxy admin can't transfer tokens they possess.
- Token owner.
- Can mint and burn tokens.
- Can transfer the "ownership" of the token contract.
- Token holder.
- Can use any function of the ERC20 interface, plus some extra non standard functions defined in the OpenZeppelin ERC20 token implementation.
It is important to note that the proxy admin cannot be a token holder nor token owner. On the other hand, the token owner can be a token holder too. There is only one token owner at any given point after block execution.
- nodejs latest LTS or above. This is currently fermium (v14).
Optional:
- jq. This is not a hard requirement but it is recommended since some shell commands suggested in this readme use it.
- Hardhat shorthand. Several snippets assume you have installed it. In general,
hh
invocations can be replaced with a Hardhat invocation throughnpx
like so:npx hardhat
To run tests or deployment you need to install the root package dependencies. To do so:
cd
to the directory where the repo is cloned.- Execute
npm install
.
There are unit tests for the token contract.
Use npm test
to run them.
There is a Hardhat task that deploys the token system. It is meant for both Rinkeby testnet and mainnet networks.
Here are step-by-step instructions for deployment. They assume an Infura JSON-RPC endpoint is used.
The deployment is meant to bail out if it detects an already existing deployment. If you want to execute a new deployment from scratch, the deployment artifact should be erased or moved elsewhere. The upgrades plugin maintains an internal manifest that tracks deployment of proxies and implementation contracts that you may want to remove too.
- Deployment artifact path:
./deployment/network-name/
- The network name is the name used for the network in your Hardhat config.
- Upgrades plugin manifest path:
./openzeppelin/network-name.json
- Sometimes, the network name may be
unknown-$chainId.json
instead if it isn't a known network.
- Sometimes, the network name may be
Two accounts are needed for deployment:
- The proxy admin. This is the same account that signs the deployment transactions.
- The token owner.
These are the steps for a successful deploy on rinkeby:
- Register an account in Infura and create a project for WDoge if you don't have one.
- Run
hh compile
to ensure the compiler output is up to date. - Set the
networks
property of your hardhat config like this:
const config: HardhatUserConfig = {
networks: {
rinkeby: {
url: "https://:[email protected]/v3/your-project-id",
accounts: ["your-hex-encoded-deployer-private-key"],
},
},
// other config fields
};
- Ensure your proxy admin address has enough ether for deployment. The total deployment cost shouldn't be higher than 2M gas.
- Invoke the Hardhat deploy task:
hh --network rinkeby wdoge.deployToken --token-owner tokenOwnerAddress
- (Optional) If you want to publish this deployment, track both the
deployment/rinkeby/deployment.json
artifact and the.openzeppelin/rinkeby.json
manifest and commit them.
It is highly recommended to verify the token implementation contract and the proxy contract at this point.
Once that is done, the deployment is finished.
You can print the proxy contract state if you want.
Three accounts are needed for deployment:
- The deployer. This is an externally owned account that signs the deployment transactions.
- The proxy admin. This should be a multisig wallet.
- The token owner. This should be a multisig wallet.
These are the steps for a successful deploy on mainnet:
- Register an account in Infura and create a project for WDoge if you don't have one.
- Run
hh compile
to ensure the compiler output is up to date. - Set the
networks
property of your hardhat config like this:
const config: HardhatUserConfig = {
networks: {
mainnet: {
url: "https://:[email protected]/v3/your-project-id",
accounts: ["your-hex-encoded-deployer-private-key"],
},
},
// other config fields
};
- Ensure your deployer address has enough ether for deployment. The total deployment cost shouldn't be higher than 2M gas. You may want to specify base fee and priority fees in the next step.
- Invoke the Hardhat deploy task:
hh --network mainnet wdoge.deployToken --token-owner tokenOwnerAddress --proxy-admin proxyAdminAddress
- (Optional) If you want to publish this deployment, track both the
deployment/mainnet/deployment.json
artifact and the.openzeppelin/mainnet.json
manifest and commit them.
It is highly recommended to verify the token implementation contract and the proxy contract at this point.
Once that is done, the deployment is finished.
You can print the proxy contract state if you want.
The deploy task has other options that may be useful in certain network conditions. All options can be consulted by invoking:
hh wdoge.deployToken --help
Some of these options are:
--token-gas-limit
: gas limit for the token implementation contract deployment.--proxy-gas-limit
: gas limit for the proxy contract deployment.--max-fee-per-gas
: maximum fee per unit of gas.--max-priority-fee-per-gas
: maximum priority fee per unit of gas.
To verify a contract on Etherscan we use the hardhat-etherscan plugin. These are the inputs needed for a successful verification:
- Network used for the deployment.
- Contract address.
- Constructor arguments, if any.
There are two types of contracts that need verification:
- Implementation contracts
- Proxy contracts
Deployment artifacts store the inputs for each of these types in a different way.
The following sections (Implementation contracts and Proxy contracts) assume that the contract verification state in etherscan is unverified and unmatched bytecode. This means that Etherscan not only doesn't know the exact source code used to compile the deployed contract but it also doesn't recognize the bytecode as similar to any other verified contract.
If Etherscan recognizes the contract bytecode as a similar match to another verified contract, refer to the similar match section for details on how to achieve an exact match verification.
Keep in mind that you can refer to the hardhat-etherscan
CLI help by running hh verify --help
or checking its README.
First of all, the address for the implementation contract is needed. We are going to verify the source code of the WDoge implementation contract.
You can get the implementation contract address for the token like this:
jq .contracts.wDoge.proxyConstructorArgs.implementationContractAddress deployment/your-network/deployment.json
Where your-network
is the name of the network you used while running the deploy task, i.e. the --network
argument.
Once you have the address of the WDoge
contract, you can run the Hardhat verify
task to verify its source code.
- Set the
etherscan
property of your hardhat config like this:
const config: HardhatUserConfig = {
etherscan: {
apiKey: "your-etherscan-api-key"
},
// other config fields
};
- Run the Hardhat verify task:
hh --network your-network verify your-WDoge-implementation-contract-address
Note that there are no constructor arguments for the WDoge
implementation contract. In general, upgradeable contracts don't use constructor arguments although this may change in the future.
Once that's done, it's verified.
We are going to verify the WDoge proxy here.
You can get the proxy contract address for the token like this:
jq .contracts.wDoge.address deployment/your-network/deployment.json
You also need the constructor arguments for the proxy. The proxy constructor has the following arguments:
constructor(address implementation, address admin, bytes memory data) {}
Note that you need these values as they were sent in the deployment transaction, it does not matter if the contract later modified the state variables associated with these parameters. For this reason, the deploy task stores the arguments into the deployment artifact.
- The implementation contract address for the
WDoge
is the same one as explained here. - The admin argument is the initial proxy admin account.
- The
data
parameter is the ABI encoded call to the initializer function in the implementation contract.
Run jq .contracts.wDoge.proxyConstructorArgs deployment/your-network/deployment.json
to get them.
With all these you can now verify the proxy contract:
- Ensure the
etherscan
property of your hardhat config contains your Etherscan API key:
const config: HardhatUserConfig = {
etherscan: {
apiKey: "your-etherscan-api-key"
},
// other config fields
};
- Run the Hardhat verify task:
hh --network your-network verify your-WDoge-proxy-contract-address --contract contracts/WDogeProxy.sol:WDogeProxy your-WDoge-implementation-contract-address proxy-admin-address encoded-initializer-arguments
This verifies the proxy source code but there is more.
Etherscan has Read as proxy
and Write as proxy
features that are enabled once the proxy has an associated implementation.
To enable these, it is necessary to request Etherscan to recognize the contract as a proxy and to detect the implementation contract address.
This can be done by going to the Contract
tab, pressing the More Options
button and choosing the Is this a proxy?
option.
It is possible that the source code for the contract was already supplied by etherscan due to it recognizing the contract bytecode.
TODO
See https://info.etherscan.com/types-of-contract-verification/ and https://info.etherscan.com/update-on-similar-match-contract-verification/
- Select your account.
- Go to
Assets
tab. - Click
Import tokens
. - Paste the proxy contract address.
- You can check you've got the correct address by looking at the address view of the proxy in Etherscan. Once there, you should see that the contract has the
WDogeProxy
contract name.
- You can check you've got the correct address by looking at the address view of the proxy in Etherscan. Once there, you should see that the contract has the
- Wait for the Token Symbol and Token Decimals to be auto detected.
- Click
Add Custom Token
. - Click
Import Tokens
.
Now you should see your token balance.
This requires you to be able to view your balance in MetaMask. You can send WDoge tokens just like you do with any other ERC20 token.
There is an upgrade task that helps prepare an upgrade so it can be finalized using a multisig wallet or cold wallet.
The upgrade process consists of the following two transactions:
- The new implementation contract deployment transaction.
- This step uses the upgrades plugin.
- The deployment transaction is skipped if there already is a deployment present in the network.
- If you want to force a redeployment, it may be necessary to alter the upgrades plugin manifest in
.openzeppelin
. - If the upgrades plugin finds an incompatibility in the new implementation contract, it will abort the deployment.
- The proxy upgrade transaction itself.
- This transaction can call an initialization function optionally. See this section for more details.
There is only one task parameter that is mandatory. The --new-implementation-contract
parameter must be set to the name of the implementation contract. If the contract name is not unique in the contracts directory, the fully qualified name may be necessary instead. E.g. the fully qualified name for DummyToken
is contracts/DummyToken.sol:DummyToken
.
Let's see an example of the task usage:
$ hh --network rinkeby wdoge.upgradeToken --new-implementation-contract DummyToken
Token implementation contract is deployed at address 0x805439bc66707b4427D03062fC8379FB84f3723B.
Other task parameters can be seen invoking hh wdoge.upgradeToken --help
.
In particular, you may be interested in specifying a migration or initialization call to be done when the upgrade is executed.
The upgrade task allows you to specify the migration contract call with a javascript module.
For example, if you have a contract like this:
struct TXO {
bytes32 txHash;
uint32 index;
}
contract Foo {
function initialize(uint version, string name, TXO memory txOutput, bytes b) public { ... }
}
The module should look like this:
module.exports = {
name: "initialize",
args: [
30,
"New name",
{
// bytes32 have to be 0x-prefixed
txHash: "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
index: 3,
},
// bytes have to be 0x-prefixed
"0xcafe",
]
};
You need to pass the path of this module to the --call-args
parameter of the wdoge.upgradeToken
task. E.g:
hh --network rinkeby wdoge.upgradeToken --new-implementation-contract Foo --call-args initialize_params.js
When invoking the upgrade task, you'll see a message like this:
Token migration call data is 0x454b0608000000000000000000000000000000000000000000000000000000000000001e
You can copy and paste the hex encoded string into the data (bytes)
field of the upgradeToAndCall
function to invoke it atomically during the upgrade transaction.
If during the upgrade preparation you find yourself with the following error:
An unexpected error occurred:
Error: Timed out waiting for implementation contract deployment to address 0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9 with transaction 0xb5f93fb9196054fad710cfd2b69fa7c48767661967e7a0f26a6438ff645a537e
Run the function again to continue waiting for the transaction confirmation. If the problem persists, adjust the polling parameters with the timeout and pollingInterval options.
then the transaction probably has a low gas price for the current network conditions. By default, the wdoge.upgradeToken
task waits for 60 seconds before timing out.
To send a transaction with higher gas price overriding the current one you can do the following:
- Assume the tx hash is
0xb5f93fb9196054fad710cfd2b69fa7c48767661967e7a0f26a6438ff645a537e
as shown in the error. - Open the manifest
.openzeppelin/your-network.json
. - Search for the tx hash in the manifest. It should look like this:
[...]
},
"9bf3ba1091887e531235ce3f59fa591318e88fc5024ac7cb2928665ac379a707": {
"address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
"txHash": "0xb5f93fb9196054fad710cfd2b69fa7c48767661967e7a0f26a6438ff645a537e",
[...]
- Erase the entire entry that contains the
txHash
field that matches the pending tx hash. The result should erase the key and value marked with an arrow:
[...]
},
"9bf3ba1091887e531235ce3f59fa591318e88fc5024ac7cb2928665ac379a707": { <---------------
"address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
"txHash": "0xb5f93fb9196054fad710cfd2b69fa7c48767661967e7a0f26a6438ff645a537e",
[...]
- Reexecute the upgrade task specifying the
--nonce
,--max-fee-per-gas
,--max-priority-fee-per-gas
options. E.g:
hh --network rinkeby wdoge.upgradeToken --new-implementation-contract DummyToken --nonce 8 --max-fee-per-gas 60 --max-priority-fee-per-gas 2
The nonce
option should be the same nonce as the one in tx 0xb5f93fb9196054fad710cfd2b69fa7c48767661967e7a0f26a6438ff645a537e
and --max-fee-per-gas
, --max-priority-fee-per-gas
should be at least 10% higher than the one in tx 0xb5f93fb9196054fad710cfd2b69fa7c48767661967e7a0f26a6438ff645a537e
.
This assumes that the proxy admin is a gnosis safe multisig contract.
- Open the gnosis safe application.
- Click "New Transaction"
- Click "Contract interaction"
- Paste the WDoge proxy address. See here to get the address.
- (Optional) This step may not be needed if the Gnosis Safe App fills in the proxy ABI for you.
Paste the
WDogeProxy
ABI. To get the ABI:- Run
hh compile
to ensure contracts are compiled. - Run
jq .abi artifacts/contracts/WDogeProxy.sol/WDogeProxy.json
to get the ABI.
- Run
- Select either
upgradeTo
orupgradeToAndCall
as the method.- If there's no migration or initialization contract call then you want the
upgradeTo
method.
- If there's no migration or initialization contract call then you want the
- Paste the new implementation contract address in the
newImplementation
textbox. - (Optional) If you're using the
upgradeToAndCall
, then- input the amount of ETH to send to the proxy contract in the
Value
textbox. Typically, this would be zero. - input the encoded migration call in the
data
textbox.
- input the amount of ETH to send to the proxy contract in the
- Click the
Review
button. - Click the
Submit
button. - Approve the transaction so it is signed.
- Wait until the transaction has enough confirmations.
Once it is confirmed, the transaction should have an Upgraded
event visible in the Logs
tab in Etherscan. You can use the proxy inspection task to check out the implementation address too.
After executing the upgrade, it may be necessary to issue an implementation contract address redetection in etherscan.
This can be done by going to the Contract
tab, pressing the More Options
button and choosing the Is this a proxy?
option.
See the verification section for proxies for more details.
You can read the WDoge proxy state by invoking the following Hardhat task:
hh --network your-network wdoge.inspectProxy
where your-network
must match the network name you used to deploy the contracts.
First, run hh compile
. Then run jq .abi artifacts/contracts/WDoge.sol/WDoge.json
.