Merge branch 'main' of github.com:web3well/bls-wallet into contract-audit

This commit is contained in:
Jacob Caban-Tomski
2022-10-24 16:09:00 -05:00
12 changed files with 185 additions and 94 deletions

View File

@@ -37,3 +37,13 @@ runAggregatorProxy(
},
);
```
## Instant wallet without dapp-sponsored transaction
![Instant wallet without dapp-sponsored transactions](./../docs/images/system-overview/instant-wallet-without-dapp-sponsored-txs.jpg)
## Instant wallet with dapp-sponsored transaction
![Instant wallet with dapp-sponsored transaction](./../docs/images/system-overview/instant-wallet-with-dapp-sponsored-txs.jpg)
## Example dApp using a proxy aggregator
- https://github.com/JohnGuilding/single-pool-dex

View File

@@ -29,6 +29,34 @@ you might have:
If you don't have a `.env`, you will need to append `--env <name>` to all
commands.
#### Environment Variables
| Name | Example Value | Description |
| ---------------------------- | ------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| RPC_URL | https://localhost:8545 | The RPC endpoint for an EVM node that the BLS Wallet contracts are deployed on |
| USE_TEST_NET | false | Whether to set all transaction's `gasPrice` to 0. Workaround for some networks |
| ORIGIN | http://localhost:3000 | The origin for the aggregator client. Used only in manual tests |
| PORT | 3000 | The port to bind the aggregator to |
| NETWORK_CONFIG_PATH | ../contracts/networks/local.json | Path to the network config file, which contains information on deployed BLS Wallet contracts |
| PRIVATE_KEY_AGG | 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | Private key for the EOA account used to submit bundles on chain |
| PRIVATE_KEY_ADMIN | 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d | Private key for the admin EOA account. Used only in tests |
| TEST_BLS_WALLETS_SECRET | test-bls-wallets-secret | Secret used to seed BLS Wallet private keys during tests |
| PG_HOST | 127.0.0.1 | Postgres database host |
| PG_PORT | 5432 | Postgres database port |
| PG_USER | bls | Postgres database user |
| PG_PASSWORD | generate-a-strong-password | Postgres database password |
| PG_DB_NAME | bls_aggregator | Postgres database name |
| BUNDLE_TABLE_NAME | bundles | Postgres table name for bundles |
| BUNDLE_QUERY_LIMIT | 100 | Maximum number of bundles returned from Postgres |
| MAX_AGGREGATION_SIZE | 12 | Maximum number of actions from bundles which will be aggregated together for submission on chain |
| MAX_AGGREGATION_DELAY_MILLIS | 5000 | Maximum amount of time in milliseconds aggregator will wait before submitting bundles on chain |
| MAX_UNCONFIRMED_AGGREGATIONS | 3 | Maximum unconfirmed bundle aggregations that will be submitted on chain. Multiplied with `MAX_AGGREGATION_SIZE` to determine maximum of unconfirmed on chain actions |
| LOG_QUERIES | false | Whether to print Postgres queries in event log.`TEST_LOGGING` must be enabled |
| TEST_LOGGING | false | Whether to print aggregator server events to stdout. Useful for debugging & logging. |
| FEE_TYPE | ether OR token:0xabcd...1234 | The fee type the aggregator will accept. Either `ether` for ETH/chains native currency or `token:0xabcd...1234` (token contract address) for an ERC20 token |
| FEE_PER_GAS | 0 | Minimum amount per gas (gasPrice) the aggregator will accept in ETH/chain native currency/ERC20 tokens |
| FEE_PER_BYTE | 0 | Minimum amount per calldata byte the aggregator will accept in ETH/chain native currency/ERC20 tokens (rollup L1 cost) |
### PostgreSQL
#### With docker-compose
@@ -170,13 +198,16 @@ deno run -r --allow-net --allow-env --allow-read --unstable ./programs/aggregato
#### Transaction reverted: function call to a non-contract account
- Is `./contracts/contracts/lib/hubble-contracts/contracts/libs/BLS.sol`'s `COST_ESTIMATOR_ADDRESS` set to the right precompile cost estimator's contract address?
- Is `./contracts/contracts/lib/hubble-contracts/contracts/libs/BLS.sol`'s
`COST_ESTIMATOR_ADDRESS` set to the right precompile cost estimator's contract
address?
- Are the BLS Wallet contracts deployed on the correct network?
- Is `NETWORK_CONFIG_PATH` in `.env` set to the right config?
#### Deno version
Make sure your Deno version is [up to date.](https://deno.land/manual/getting_started/installation#updating)
Make sure your Deno version is
[up to date.](https://deno.land/manual/getting_started/installation#updating)
### Notable Components

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

View File

@@ -58,6 +58,8 @@ make these changes in aggregator > .env
RPC_URL=http://localhost:8545
NETWORK_CONFIG_PATH=../contracts/networks/local.json
In a seperate terminal/shell instance
```sh
docker-compose up -d postgres # Or see local postgres instructions in ./aggregator/README.md#PostgreSQL
cd ./aggregator

View File

@@ -8,7 +8,7 @@ Follow the instructions for [Local Development](./local_development.md), replaci
### Deployer account
BLS Wallet contract deploys use `CREATE2` to maintain consistent addresses across networks. As such, a create2 deployer contract is used and listed in `./contracts/.env` under the environment variables `DEPLOYER_MNEMONIC` & `DEPLOYER_SET_INDEX`. The HD address will need to be funded in order to deploy the contracts.
BLS Wallet contract deploys use `CREATE2` to maintain consistent addresses across networks. As such, a create2 deployer contract is used and listed in `./contracts/.env` under the environment variables `DEPLOYER_MNEMONIC` & `DEPLOYER_SET_INDEX`. The hierarchical deterministic (HD) wallet address will need to be funded in order to deploy the contracts.
If you do not need consistent addresses, for example on a local or testnet network, you can replace the `DEPLOYER_MNEMONIC` with another seed phrase which already has a funded account.
@@ -66,40 +66,27 @@ PRIVATE_KEY_ADMIN=PK1
### Extension
Check the [controller constants file](../extension/source/Controllers/constants.ts) to see if your network is already added. If not, you will need to add chainid & supported networks entries for your network/chain. These changes can be committed.
Check the [`config.json` file](../extension//config.json) to see if your network is already added. If not, you will need to add the relevant properties for your network/chain. These changes can be committed.
Then, update this value in `./extension/.env`.
```
...
## Example: Arbitrum Testnet (Arbitrum Goerli Testnet)
DEFAULT_CHAIN_ID=YOUR_CHAIN_ID
...
```
You will need two ETH addresses with Abitrum Goerli ETH and their private keys (PRIVATE_KEY_AGG & PRIVATE_KEY_ADMIN) for running the aggregator. It is **NOT** recommended that you use any primary wallets with ETH Mainnet assets.
## Run
Follow the remaing instruction in [Local Development](./local_development.md) starting with the `Run` section.
## Example: Arbitrum Testnet (Rinkeby Arbitrum Testnet)
You will need two ETH addresses with Rinkeby ETH and their private keys (PK0 & PK1) for running the aggregator. It is NOT recommended that you use any primary wallets with ETH Mainnet assets.
You can get Rinkeby ETH at https://app.mycrypto.com/faucet, and transfer it into the Arbitrum testnet via https://bridge.arbitrum.io/. Make sure when doing so that your network is set to Rinkeby in your web3 wallet extension, such as MetaMask.
You can get Goerli ETH at https://goerlifaucet.com/ or https://app.mycrypto.com/faucet, and transfer it into the Arbitrum testnet via https://bridge.arbitrum.io/. Make sure when doing so that your network is set to Goerli in your web3 wallet extension, such as MetaMask.
Update these values in `./aggregator/.env`.
```
RPC_URL=https://rinkeby.arbitrum.io/rpc
RPC_URL=https://goerli-rollup.arbitrum.io/rpc
...
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-testnet.json
NETWORK_CONFIG_PATH=../contracts/networks/arbitrum-goerli.json
PRIVATE_KEY_AGG=PK0
PRIVATE_KEY_ADMIN=PK1
...
```
And then update this value in `./extension/.env`.
```
And then ensure the `defaultNetwork` value in `./extension/config.json` is set to `arbitrum-goerli`.
```json
...
DEFAULT_CHAIN_ID=421611
"defaultNetwork": "arbitrum-goerli",
...
```
```

View File

@@ -30,7 +30,7 @@ Next, connect your dApp to Quill just like you would any other extension wallet.
```typescript
import { providers } from 'ethers';
const provider = providers.Web3Provider(window.ethereum);
const provider = new providers.Web3Provider(window.ethereum);
await window.ethereum.request({ method: "eth_accounts" });
```
@@ -113,6 +113,7 @@ See the [System Overview](./system_overview.md) for more details on what's happe
- https://github.com/kautukkundan/BLSWallet-ERC20-demo
- https://github.com/voltrevo/bls-wallet-billboard
- https://github.com/JohnGuilding/single-pool-dex
## Coming soon

View File

@@ -14,7 +14,7 @@ const TransactionCard: React.FC<SendTransactionParams> = ({
gas,
gasPrice,
}) => {
const { loading, method } = useInputDecode(data || '0x', to);
const { loading, method, args } = useInputDecode(data || '0x');
return (
<div className="bg-white rounded-md p-4 border border-blue-400">
@@ -37,7 +37,16 @@ const TransactionCard: React.FC<SendTransactionParams> = ({
<div className="break-all">
details:{' '}
<span className="font-bold">{loading ? 'loading...' : method}</span>
<div className="text-[9pt] mt-2 font-normal">{data}</div>
<div className="text-[9pt] mt-2 font-mono">
{loading
? 'loading params...'
: // prettier-ignore
args.map((arg, i) => (
<div key={arg}>
{i + 1}. {arg.toString()}
</div>
))}
</div>
</div>
</div>

View File

@@ -1,5 +1,7 @@
import { ethers } from 'ethers';
import { FunctionComponent, useEffect, useState } from 'react';
import { FilePlus, Cardholder, Warning } from 'phosphor-react';
import Button from '../../components/Button';
import ReviewSecretPhrasePanel from './ReviewSecretPhrasePanel';
import ViewSecretPhrasePanel from './ViewSecretPhrasePanel';
@@ -8,14 +10,109 @@ const SecretPhrasePanel: FunctionComponent<{
secretPhrase?: string[];
}> = () => {
const [mnemonic, setMnemonic] = useState<string[]>([]);
const [mnemonicInput, setMnemonicInput] = useState<string>('');
const [error, setError] = useState<boolean>(false);
useEffect(() => {
if (mnemonicInput !== '') {
if (mnemonicInput.split(' ').length !== 12) {
setError(true);
return;
}
try {
ethers.Wallet.fromMnemonic(mnemonicInput);
setError(false);
} catch (e) {
setError(true);
}
}
}, [mnemonicInput]);
const createMnemonic = () => {
const mnemonicPhrase = ethers.Wallet.createRandom().mnemonic.phrase;
setMnemonic(mnemonicPhrase.split(' '));
}, []);
};
const validateAndSetMnemonic = (m: string) => {
const split = m.split(' ');
if (!error) {
setMnemonic(split);
}
};
const [inReview, setInReview] = useState(false);
if (mnemonic.length === 0) {
return (
<div className="">
<div className="mb-10">
<div className="font-bold">One Last step!</div>
<span>
You can use an existing seed phrase to generate BLS keypair,
Otherwise we will create a new fresh one for you
</span>
</div>
<div className="flex flex-col justify-center align-middle gap-3">
<Button
className="btn-primary"
onPress={createMnemonic}
iconLeft={<FilePlus size={20} />}
>
Generate New
</Button>
<div className="text-center text-grey-800"> - OR - </div>
<div className="">
<textarea
className={[
'mt-2',
'bg-opacity-5',
'border-opacity-25',
'focus:border-opacity-25',
].join(' ')}
placeholder="existing 12 word Mnemonic (space separated)"
onChange={(e) => {
setMnemonicInput(e.target.value);
}}
/>
{mnemonicInput !== '' && !error && (
<Button
className="btn-secondary"
onPress={() => validateAndSetMnemonic(mnemonicInput)}
iconLeft={<Cardholder size={20} />}
>
Use Existing Mnemonic
</Button>
)}
{error && (
<div
className={[
'bg-alert-400',
'p-4',
'mt-4',
'text-[10pt]',
'rounded-md',
'flex',
'gap-4',
'bg-opacity-20',
].join(' ')}
>
<Warning className="text-alert-500 mt-1" size={64} />
<div className="align-text-top">
Please enter correct 12 word mnemonic compatible with BIP-39
standard, separated by space.
</div>
</div>
)}
</div>
</div>
</div>
);
}
if (!inReview) {
return (
<ViewSecretPhrasePanel

View File

@@ -2,92 +2,44 @@ import { ethers } from 'ethers';
import { useEffect, useState } from 'react';
import axios from 'axios';
import assert from '../helpers/assert';
import { useQuill } from '../QuillContext';
import { IReadableCell } from '../cells/ICell';
const getParitySigRegistry = async (
provider: IReadableCell<ethers.providers.Provider>,
) => {
const address = '0x44691B39d1a75dC4E0A0346CBB15E310e6ED1E86';
const abi = [
{
constant: true,
inputs: [{ name: '', type: 'bytes4' }],
name: 'entries',
outputs: [{ name: '', type: 'string' }],
payable: false,
type: 'function',
},
];
const getMethodFromRegistry = async (data: string) => {
if (data === '0x') return { funcSig: 'SENDING ETH', args: [] };
return new ethers.Contract(address, abi, await provider.read());
};
const funcSigHex = data.slice(0, 10);
const getMethodFromOnChainRegistry = async (
data: string,
provider: IReadableCell<ethers.providers.Provider>,
) => {
if (data === '0x') return 'SENDING ETH';
const methodID = ethers.utils.hexDataSlice(data, 0, 4);
const registry = await getParitySigRegistry(provider);
return registry.entries(methodID);
};
const getMethodFromEtherscan = async (to: string, data: string) => {
const res = await axios.get(
`https://api.etherscan.io/api?module=contract&action=getabi&address=${to}`,
`https://sig.eth.samczsun.com/api/v1/signatures?all=true&function=${funcSigHex}`,
);
const funcSig = res.data.result.function[funcSigHex][0].name;
const iface = new ethers.utils.Interface([`function ${funcSig}`]);
const { args } = iface.parseTransaction({ data });
if (res.data.result !== 'Contract source code not verified') {
const iface = new ethers.utils.Interface(res.data.result);
return iface.parseTransaction({ data, value: 1 }).name;
}
assert(false, () => new Error('Unverified Contract'));
return { funcSig, args };
};
const formatMethod = (method: string) =>
method
.split('(')[0]
.replace(/([a-z](?=[A-Z]))/g, '$1 ')
.toUpperCase();
type UseInputDecodeValues = {
loading: boolean;
method: string;
args: ethers.utils.Result;
};
export const useInputDecode = (
functionData: string,
to: string,
): UseInputDecodeValues => {
export const useInputDecode = (functionData: string): UseInputDecodeValues => {
const quill = useQuill();
const [loading, setLoading] = useState<boolean>(true);
const [method, setMethod] = useState<string>('CONTRACT INTERACTION');
const [allArgs, setAllArgs] = useState<ethers.utils.Result>([]);
useEffect(() => {
const getMethod = async () => {
setLoading(true);
const data = functionData?.replace(/\s+/g, '');
try {
const registryPromise = getMethodFromOnChainRegistry(
data,
quill.ethersProvider,
);
const etherScanPromise = getMethodFromEtherscan(to, data);
const rawMethod = (await registryPromise) ?? (await etherScanPromise);
if (rawMethod) {
setMethod(formatMethod(rawMethod));
}
} catch (error) {
console.log({ error });
}
const { funcSig, args } = await getMethodFromRegistry(data);
setMethod(funcSig);
setAllArgs(args);
setLoading(false);
};
@@ -95,7 +47,7 @@ export const useInputDecode = (
if (functionData) {
getMethod();
}
}, [functionData, to, quill]);
}, [functionData, quill]);
return { loading, method };
return { loading, method, args: allArgs };
};

View File

@@ -1,4 +1,5 @@
@import url('https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;500;600&display=swap');
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+Mono&display=swap');
@tailwind base;
@tailwind components;

View File

@@ -48,6 +48,7 @@ module.exports = {
},
fontFamily: {
sans: ['Montserrat'],
mono: ['Noto Sans Mono'],
},
fontSize: {
disclaimer: '12pt',