Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for sapphire-paratime 2.x #16

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions .github/workflows/ci-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
name: Install pnpm
id: pnpm-install
with:
version: 8
version: 9
run_install: false

- name: Get pnpm store directory
Expand Down Expand Up @@ -57,15 +57,13 @@ jobs:
runs-on: ubuntu-latest
services:
sapphire-localnet-ci:
image: ghcr.io/oasisprotocol/sapphire-localnet:latest
image: ghcr.io/oasisprotocol/sapphire-localnet
ports:
- 8545:8545
- 8546:8546
env:
OASIS_DEPOSIT: /oasis-deposit -test-mnemonic -n 5
options: >-
--rm
--health-cmd="/oasis-node debug control wait-ready -a unix:/serverdir/node/net-runner/network/client-0/internal.sock"
--health-cmd="test -f /CONTAINER_READY"
--health-start-period=90s
steps:
- name: Checkout code
Expand Down
94 changes: 50 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@

This is a skeleton for confidential Oasis dApps:

- `backend` contains the example MessageBox solidity contract, deployment and
testing utils.
- `frontend` contains a Vue-based web application communicating with the
backend smart contract.
- `backend` contains the example MessageBox Solidity contract and Hardhat utils
for deploying the contract and managing it via command line.
- `frontend` contains a Vue-based web application which communicates with your
smart contract.

This monorepo is set up for `pnpm`. Install dependencies by running:

Expand All @@ -21,50 +21,68 @@ Move to the `backend` folder and build smart contracts:
pnpm build
```

Then, prepare your hex-encoded private key for paying the deployment gas fee
and store it as an environment variable:
### Localnet deployment and Testing

Spin up the [Sapphire Localnet] image:

```shell
export PRIVATE_KEY=0x...
docker run -it -p8544-8548:8544-8548 ghcr.io/oasisprotocol/sapphire-localnet
```

Alternative CMD command for Windows:
Once Localnet is ready, deploy the contract using the first test account by
invoking:

```shell
export PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
npx hardhat deploy localhost --network sapphire-localnet
```
set PRIVATE_KEY=0x...

Similarly, you can run tests on Localnet:

```shell
npx hardhat test --network sapphire-localnet
```

To deploy the contracts to the [Sapphire Localnet], Testnet or Mainnet, use
one of the following commands:
### Production deployment

Prepare your hex-encoded private key for paying the deployment gas fee and store
it as an environment variable:

```shell
npx hardhat deploy --network sapphire-localnet
npx hardhat deploy --network sapphire-testnet
npx hardhat deploy --network sapphire
export PRIVATE_KEY=0x...
```

If you don't need any Sapphire-specific precompiles, you can spin up the
hardhat node and deploy the contract locally:
Alternative CMD command for Windows:

```powershell
set PRIVATE_KEY=0x...
```
npx hardhat node
npx hardhat deploy --network localhost

To deploy the contract on Testnet or Mainnet for your dApp that will be
accessible on `yourdomain.com`:

```shell
npx hardhat deploy yourdomain.com --network sapphire-testnet
npx hardhat deploy yourdomain.com --network sapphire
```

Once deployed, the MessageBox address will be reported. Remember it and store it
inside the `frontend` folder's `.env.development`, for example:
[Sapphire Localnet]: https://github.com/oasisprotocol/oasis-web3-gateway/pkgs/container/sapphire-localnet

## Frontend

Once the contract is deployed, the MessageBox address will be reported. Store it
inside the `frontend` folder's `.env.development` (for Localnet) or
`.env.production` (for Testnet or Mainnet - uncomment the appropriate network),
for example:

```
VITE_MESSAGE_BOX_ADDR=0x5FbDB2315678afecb367f032d93F642f64180aa3
```

[Sapphire Localnet]: https://github.com/oasisprotocol/oasis-web3-gateway/pkgs/container/sapphire-dev
### Run locally

## Frontend

After you compiled the backend, updated `.env.development` with the
corresponding address and a chain ID, move to the `frontend` folder, compile
and Hot-Reload frontend for Development:
Run the hot-reload version of the frontend configured in `.env.development` by
running:

```sh
pnpm dev
Expand All @@ -75,15 +93,13 @@ browsers (e.g. Brave) may require https connection and a CA-signed certificate
to access the wallet. In this case, read the section below on how to properly
deploy your dApp.

You can use one of the deployed test accounts and associated private key with
MetaMask. If you use the same MetaMask accounts on fresh local networks such as
Hardhat Node, Foundry Anvil or sapphire-dev docker image, don't forget to
*clear your account's activity* each time or manually specify the correct
account nonce.
Note: If you use the same MetaMask accounts in your browser and restart the
sapphire-dev docker image, don't forget to *clear your MetaMask activity* each
time to fetch correct account nonce.

### Frontend Deployment
### Production deployment

You can build assets for deployment by running:
Build assets for deployment by running:

```sh
pnpm build
Expand All @@ -93,7 +109,7 @@ pnpm build

#### Different Website Base

If you are running dApp on a non-root base dir, add
If run dApp on a non-root base dir, add

```
BASE_DIR=/my/public/path
Expand All @@ -106,13 +122,3 @@ pnpm build-only --base=/my/public/path/
```

Then copy the `dist` folder to a place of your `/my/public/path` location.

## Troubleshooting

When click button Connect Wallet, in some case you will constantly get the error:
`Uncaught (in promise) Error: [useEthereumStore] Request account failed!`

To resolve it, try to open MetaMask and manually connect site:

![MetaMask: Connected sites](docs/manually-connect-site-1.png)
![MetaMask: Manually connect to the current site](docs/manually-connect-site-2.png)
24 changes: 18 additions & 6 deletions backend/contracts/MessageBox.sol
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MessageBox {
import "@oasisprotocol/sapphire-contracts/contracts/auth/SiweAuth.sol";

contract MessageBox is SiweAuth {
string private _message;
address public author;


modifier isAuthor(bytes calldata bearer) {
// Use msg.sender for transactions and signed calls, fallback to
// checking bearer.
if (msg.sender != author && authMsgSender(bearer) != author) {
revert("not allowed");
}
_;
}

constructor(string memory domain) SiweAuth(domain) {
}

function setMessage(string calldata in_message) external {
_message = in_message;
author = msg.sender;
}

function message() external view returns (string memory) {
if (msg.sender!=author) {
revert("not allowed");
}
return _message;
function message(bytes calldata bearer) external view isAuthor(bearer) returns (string memory) {
return _message;
}
}
20 changes: 17 additions & 3 deletions backend/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {JsonRpcProvider} from "ethers";
import 'hardhat-watcher';
import { TASK_COMPILE } from 'hardhat/builtin-tasks/task-names';
import { HardhatUserConfig, task } from 'hardhat/config';
import {SiweMessage} from 'siwe';
import 'solidity-coverage';

const TASK_EXPORT_ABIS = 'export-abis';
Expand Down Expand Up @@ -39,13 +40,14 @@ task(TASK_EXPORT_ABIS, async (_args, hre) => {

// Unencrypted contract deployment.
task('deploy')
.addPositionalParam('domain', 'dApp domain which Metamask will be allowed for signing-in')
.setAction(async (args, hre) => {
await hre.run('compile');

// For deployment unwrap the provider to enable contract verification.
const uwProvider = new JsonRpcProvider(hre.network.config.url);
const MessageBox = await hre.ethers.getContractFactory('MessageBox', new hre.ethers.Wallet(accounts[0], uwProvider));
const messageBox = await MessageBox.deploy();
const messageBox = await MessageBox.deploy(args.domain);
await messageBox.waitForDeployment();

console.log(`MessageBox address: ${await messageBox.getAddress()}`);
Expand All @@ -59,7 +61,19 @@ task('message')
await hre.run('compile');

const messageBox = await hre.ethers.getContractAt('MessageBox', args.address);
const message = await messageBox.message();
const domain = await messageBox.domain();

const acc = new hre.ethers.Wallet(accounts[0], hre.ethers.provider);
const siweMsg = new SiweMessage({
domain,
address: await acc.getAddress(),
uri: domain.includes(':')?domain:`http://${domain}`,
version: "1",
chainId: Number((await hre.ethers.provider.getNetwork()).chainId)
}).toMessage();
const sig = hre.ethers.Signature.from(await acc.signMessage(siweMsg));
const bearer = await messageBox.login(siweMsg, sig);
const message = await messageBox.message(bearer);
const author = await messageBox.author();
console.log(`The message is: ${message}, author: ${author}`);
});
Expand Down Expand Up @@ -99,7 +113,7 @@ const config: HardhatUserConfig = {
accounts,
},
'sapphire-testnet': {
url: 'https://testnet.sapphire.oasis.dev',
url: 'https://testnet.sapphire.oasis.io',
chainId: 0x5aff,
accounts,
},
Expand Down
6 changes: 4 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@
"ethers": "^6.10.0"
},
"devDependencies": {
"@nomicfoundation/hardhat-chai-matchers": "^2.0.7",
"@nomicfoundation/hardhat-ethers": "^3.0.5",
"@oasisprotocol/sapphire-contracts": "^0.2.7",
"@oasisprotocol/sapphire-hardhat": "^2.19.4",
"@oasisprotocol/sapphire-contracts": "^0.2.11",
"@oasisprotocol/sapphire-hardhat": "^2.22.2-next.1",
"@typechain/ethers-v6": "^0.5.1",
"@typechain/hardhat": "^9.1.0",
"@types/chai": "^4.3.4",
Expand All @@ -56,6 +57,7 @@
"npm-run-all": "^4.1.5",
"prettier": "^2.8.4",
"prettier-plugin-solidity": "1.1.2",
"siwe": "^2.3.2",
"solhint": "^3.4.0",
"solidity-coverage": "^0.8.2",
"ts-node": "^10.9.1",
Expand Down
41 changes: 37 additions & 4 deletions backend/test/MessageBox.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,53 @@
import { expect } from "chai";
import { ethers } from "hardhat";
import { config, ethers } from "hardhat";
import {SiweMessage} from "siwe";
import "@nomicfoundation/hardhat-chai-matchers";

describe("MessageBox", function () {
async function deployMessageBox() {
const MessageBox_factory = await ethers.getContractFactory("MessageBox");
const messageBox = await MessageBox_factory.deploy();
const messageBox = await MessageBox_factory.deploy("localhost");
await messageBox.waitForDeployment();
return { messageBox };
}

it("Should set message", async function () {
async function getSiweMsg(account: ethers.HDNodeWallet): Promise<string> {
return new SiweMessage({
domain: "localhost",
address: await account.getAddress(),
statement: "I accept the ExampleOrg Terms of Service: http://localhost/tos",
uri: "http://localhost:5173",
version: "1",
chainId: Number((await ethers.provider.getNetwork()).chainId),
}).toMessage();
}

it("Should set message authenticated", async function () {
// Skip this test on non-sapphire chains.
// On-chain encryption and/or signing required for SIWE.
if ((await ethers.provider.getNetwork()).chainId == 1337n) {
this.skip();
}

const {messageBox} = await deployMessageBox();

await messageBox.setMessage("hello world");

expect(await messageBox.message()).to.equal("hello world");
// Check, if author is correctly set.
expect(await messageBox.author()).to.equal(await (await ethers.provider.getSigner(0)).getAddress());
// Author should be able to read a message.
const accounts = config.networks.hardhat.accounts;
const account = ethers.HDNodeWallet.fromMnemonic(ethers.Mnemonic.fromPhrase(accounts.mnemonic), accounts.path+'/0');
const siweMsg = await getSiweMsg(account);
const sig = ethers.Signature.from(await account.signMessage(siweMsg));
const bearer = await messageBox.login(siweMsg, sig);
await expect(await messageBox.message(bearer)).to.be.equal("hello world");

// Anyone else trying to read the message should fail.
const acc2 = ethers.HDNodeWallet.fromMnemonic(ethers.Mnemonic.fromPhrase(accounts.mnemonic), accounts.path+'/1');
const siweMsg2 = await getSiweMsg(acc2);
const sig2 = ethers.Signature.from(await acc2.signMessage(siweMsg2))
const bearer2 = await messageBox.login(siweMsg2, sig2);
await expect(messageBox.message(bearer2)).to.be.reverted;
});
});
Binary file removed docs/manually-connect-site-1.png
Binary file not shown.
Binary file removed docs/manually-connect-site-2.png
Binary file not shown.
6 changes: 2 additions & 4 deletions frontend/.env.development
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# VITE_NETWORK=0x5afd
# VITE_WEB3_GATEWAY=http://localhost:8545
VITE_NETWORK=0x5aff
VITE_WEB3_GATEWAY=https://testnet.sapphire.oasis.dev
VITE_NETWORK=0x5afd
VITE_WEB3_GATEWAY=http://localhost:8545
VITE_MESSAGE_BOX_ADDR=0x2dE080e97B0caE9825375D31f5D0eD5751fDf16D
4 changes: 3 additions & 1 deletion frontend/.env.production
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
VITE_NETWORK=0x5aff
VITE_WEB3_GATEWAY=https://testnet.sapphire.oasis.dev
VITE_WEB3_GATEWAY=https://testnet.sapphire.oasis.io
#VITE_NETWORK=0x5afe
#VITE_WEB3_GATEWAY=https://sapphire.oasis.io
VITE_MESSAGE_BOX_ADDR=0xca5B5E9371e6AedD850a74554a53BA91D2D3508d
BASE_DIR=/
4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
"@metamask/detect-provider": "^2.0.0",
"@metamask/jazzicon": "^2.0.0",
"@oasisprotocol/demo-starter-backend": "workspace:^",
"@oasisprotocol/sapphire-paratime": "^1.3.2",
"@oasisprotocol/sapphire-contracts": "^0.2.11",
"@oasisprotocol/sapphire-paratime": "^2.0.1",
"ethers": "^6.10.0",
"pinia": "^2.0.28",
"siwe": "^2.3.2",
"vue": "^3.2.45",
"vue-content-loader": "^2.0.1",
"vue-router": "^4.1.6"
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ export function useMessageBox(): ComputedRef<MessageBox | null> {
return null;
}

if (!eth.signer) {
console.error('[useMessageBox] Signer is not initialized');
if (!eth.provider) {
console.error('[useMessageBox] Provider is not initialized');
return null;
}

return MessageBox__factory.connect(addr, eth.signer as ContractRunner);
return MessageBox__factory.connect(addr, eth.unwrappedSigner as ContractRunner);
});
}

Expand Down
Loading