Skip to content
Draft
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# IDE
.DS_Store
4 changes: 3 additions & 1 deletion mist-invoice-contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@
node_modules
cache
out
broadcast
broadcast
.yarn
src/types
14 changes: 5 additions & 9 deletions mist-invoice-contracts/script/createMistInvoice.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ contract InvoiceCreationScript is Script {
uint256 merkleRoot;
uint256 providerHash;
uint256 clientHash;
bytes encData;
bytes encClientKey;
bytes encProviderKey;
bytes[] encData;
}

function getSepoliaDummyData() public view returns (bytes memory) {
Expand Down Expand Up @@ -85,15 +83,15 @@ contract InvoiceCreationScript is Script {

address mistWrapperDeployment = vm.envAddress("MIST_INVOICE_ESCROW_WRAPPER");
MistInvoiceEscrowWrapper mistWrapper = MistInvoiceEscrowWrapper(mistWrapperDeployment);
bytes[] memory encDataArray = new bytes[](1);
encDataArray[0] = abi.encodePacked("dummyEncData");

// Dummy data
MistSecret memory _mistData = MistSecret({
merkleRoot: uint256(keccak256(abi.encodePacked("dummyMerkleRoot"))),
clientHash: uint256(keccak256(abi.encodePacked("dummyClientRandom"))),
providerHash: uint256(keccak256(abi.encodePacked("dummyProviderRandom"))),
encData: abi.encodePacked("dummyEncData"),
encClientKey: abi.encodePacked("dummyClientKey"),
encProviderKey: abi.encodePacked("dummyProviderKey")
encData: encDataArray
});

uint256[] memory _amounts = new uint256[](1);
Expand All @@ -105,9 +103,7 @@ contract InvoiceCreationScript is Script {
merkleRoot: _mistData.merkleRoot,
clientHash: _mistData.clientHash,
providerHash: _mistData.providerHash,
encData: _mistData.encData,
encClientKey: _mistData.encClientKey,
encProviderKey: _mistData.encProviderKey
encData: _mistData.encData
});

address invoiceAddress = mistWrapper.createInvoice(mistDataForWrapper, _amounts, _data, _type);
Expand Down
121 changes: 85 additions & 36 deletions mist-invoice-contracts/src/MistInvoiceEscrowWrapper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,25 @@ import "./libraries/PoolStructs.sol";

contract MistInvoiceEscrowWrapper is Verifier {
uint256 constant SNARK_SCALAR_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
uint256 public constant CLIENT_SIGNAL = uint256(keccak256("client")) / SNARK_SCALAR_FIELD;
uint256 public constant PROVIDER_SIGNAL = uint256(keccak256("provider")) / SNARK_SCALAR_FIELD;
uint256 public constant CLIENT_SIGNAL = uint256(keccak256("client")) % SNARK_SCALAR_FIELD;
uint256 public constant PROVIDER_SIGNAL = uint256(keccak256("provider")) % SNARK_SCALAR_FIELD;

address public immutable INVOICE_FACTORY;
IMISTPool public mistPool;

mapping(address => MistSecret) mistSecrets;

enum ROLE {CLIENT, PROVIDER}
enum ROLE {
CLIENT,
PROVIDER
}

// TODO how many bytes?
struct MistSecret {
uint256 merkleRoot; // contains client and provider addresses
uint256 providerHash;
uint256 clientHash;
bytes encData; // symmetric key for encrypted invoice _details, client address and random, provider address and random
bytes encClientKey;
bytes encProviderKey;
bytes[] encData; // abi.encode(tuple(string encryptedData, string encryptedSenderKey, string encryptedReceiverKey))
}

constructor(address _invoiceFactory, address _mistPool) {
Expand Down Expand Up @@ -62,31 +63,42 @@ contract MistInvoiceEscrowWrapper is Verifier {
}

// TODO is reentrency guard needed?
function privateRelease(address _invoiceAddr, uint256 _milestone, bytes calldata _encNote, uint256[4] calldata _digest, Proof calldata _proof) external {
function privateRelease(
address _invoiceAddr,
uint256 _milestone,
uint256[4] calldata _digest,
Proof calldata _proof
) external {
require(_invoiceAddr != address(0), "invalid invoice required");
require(_encNote.length > 0, "encrypted note required");
// TODO verify proof
require(verify(_proof, mistSecrets[_invoiceAddr].merkleRoot, _digest, CLIENT_SIGNAL), "invalid proof");
require(
verify(_proof, mistSecrets[_invoiceAddr].merkleRoot, _digest, CLIENT_SIGNAL),
"invalid proof"
);
// TODO call release on _invoiceAddr
ISmartInvoiceEscrow escrow = ISmartInvoiceEscrow(_invoiceAddr);
IERC20 token = IERC20(escrow.token());
uint256 preBalance = token.balanceOf(address(this));
// uint256 preBalance = token.balanceOf(address(this));
uint256 currentMilestone = escrow.milestone();
escrow.release(_milestone);
uint256 postBalance = token.balanceOf(address(this));
// uint256 postBalance = token.balanceOf(address(this));
// TODO save amount to provider data
uint256 providerOwed = postBalance - preBalance;
// uint256 providerOwed = postBalance - preBalance;
// TODO deposit to MIST pool
PreCommitment[] memory preCommitments = new PreCommitment[](1);
preCommitments[0] = PreCommitment({
receiverHash: mistSecrets[_invoiceAddr].providerHash,
encryptedNote: _encNote,
tokenData: TokenData({
standard: TokenStandard.ERC20,
token: address(token),
identifier: 0,
amount: providerOwed
})
});
uint256 length = _milestone - currentMilestone + 1;
PreCommitment[] memory preCommitments = new PreCommitment[](length);
for (uint256 i = 0; i < length; i++) {
preCommitments[i] = PreCommitment({
receiverHash: mistSecrets[_invoiceAddr].providerHash,
encryptedNote: mistSecrets[_invoiceAddr].encData[i + currentMilestone],
tokenData: TokenData({
standard: TokenStandard.ERC20,
token: address(token),
identifier: 0,
amount: escrow.amounts()[i + currentMilestone]
})
});
}
DepositData memory depositData = DepositData({
nonce: mistPool.getNonce(address(this)),
sender: address(this),
Expand All @@ -95,37 +107,66 @@ contract MistInvoiceEscrowWrapper is Verifier {
mistPool.deposit(depositData, bytes(""));
}

function privateDispute(address _invoiceAddr, bytes32 _details, uint256[4] calldata _digest, ROLE _role, Proof calldata _proof) external {
function privateDispute(
address _invoiceAddr,
bytes32 _details,
uint256[4] calldata _digest,
ROLE _role,
Proof calldata _proof
) external {
require(_invoiceAddr != address(0), "valid invoice required");
require(_role == ROLE.CLIENT || _role == ROLE.PROVIDER, "valid role required");
require(
_role == ROLE.CLIENT || _role == ROLE.PROVIDER,
"valid role required"
);
// TODO verify proof
if (_role == ROLE.CLIENT) {
require(verify(_proof, mistSecrets[_invoiceAddr].clientHash, _digest, CLIENT_SIGNAL), "invalid proof");
require(
verify(_proof, mistSecrets[_invoiceAddr].merkleRoot, _digest, CLIENT_SIGNAL),
"invalid proof"
);
} else {
require(verify(_proof, mistSecrets[_invoiceAddr].providerHash, _digest, PROVIDER_SIGNAL), "invalid proof");
require(
verify(_proof, mistSecrets[_invoiceAddr].merkleRoot, _digest, PROVIDER_SIGNAL),
"invalid proof"
);
}
// TODO call lock on _invoiceAddr
ISmartInvoiceEscrow escrow = ISmartInvoiceEscrow(_invoiceAddr);
escrow.lock(_details);
}

// TODO other resolve arguments needed
function resolve(address _invoiceAddr, uint256 _clientAward, uint256 _providerAward, bytes32 _details, bytes[2] calldata _encNotes) external {
function resolve(
address _invoiceAddr,
uint256 _clientAward,
uint256 _providerAward,
bytes32 _details,
bytes[2] calldata _encNotes
) external {
require(_invoiceAddr != address(0), "valid invoice required");
require(_encNotes[0].length > 0 && _encNotes[1].length > 0, "encrypted notes required");
require(
_encNotes[0].length > 0 && _encNotes[1].length > 0,
"encrypted notes required"
);
// TODO calculate client, provider, arb splits; update mappings
ISmartInvoiceEscrow escrow = ISmartInvoiceEscrow(_invoiceAddr);
IERC20 token = IERC20(escrow.token());
uint256 preBalance = token.balanceOf(address(this));
// TODO call invoice resolve
_invoiceAddr.delegatecall(
abi.encodeWithSelector(ISmartInvoiceEscrow.resolve.selector, _clientAward, _providerAward, _details)
abi.encodeWithSelector(
ISmartInvoiceEscrow.resolve.selector,
_clientAward,
_providerAward,
_details
)
);
uint256 postBalance = token.balanceOf(address(this));
// TODO save amount to provider data
// uint256 totalOwed = postBalance - preBalance;
uint256 forClient = _clientAward / (_clientAward + _providerAward) * (postBalance - preBalance);
uint256 forProvider = _providerAward / (_clientAward + _providerAward) * (postBalance - preBalance);
uint256 forClient = (_clientAward / (_clientAward + _providerAward)) * (postBalance - preBalance);
uint256 forProvider = (_providerAward / (_clientAward + _providerAward)) * (postBalance - preBalance);
// TODO deposit to MIST pool for client + provider
PreCommitment[] memory preCommitments = new PreCommitment[](2);
preCommitments[0] = PreCommitment({
Expand Down Expand Up @@ -157,12 +198,20 @@ contract MistInvoiceEscrowWrapper is Verifier {
}

// Only works for primary token, withdrawTokens is not implemented for hackathon
function privateWithdraw(address _invoiceAddr, uint256[4] calldata _digest, Proof calldata _proof, bytes calldata _encNote) external {
function privateWithdraw(
address _invoiceAddr,
uint256[4] calldata _digest,
Proof calldata _proof,
bytes calldata _encNote
) external {
require(_invoiceAddr != address(0), "valid invoice required");
require(_encNote.length > 0, "encrypted note required");
// TODO verify withdraw enabled
// TODO verify proof of client
require(verify(_proof, mistSecrets[_invoiceAddr].clientHash, _digest, CLIENT_SIGNAL), "invalid proof");
require(
verify(_proof, mistSecrets[_invoiceAddr].merkleRoot, _digest, CLIENT_SIGNAL),
"invalid proof"
);
// TODO call withdraw on invoice

ISmartInvoiceEscrow escrow = ISmartInvoiceEscrow(_invoiceAddr);
Expand All @@ -176,7 +225,7 @@ contract MistInvoiceEscrowWrapper is Verifier {
// mistPool.deposit(owed)
PreCommitment[] memory preCommitments = new PreCommitment[](1);
preCommitments[0] = PreCommitment({
receiverHash: mistSecrets[_invoiceAddr].providerHash,
receiverHash: mistSecrets[_invoiceAddr].clientHash,
encryptedNote: _encNote,
tokenData: TokenData({
standard: TokenStandard.ERC20,
Expand All @@ -192,4 +241,4 @@ contract MistInvoiceEscrowWrapper is Verifier {
});
mistPool.deposit(depositData, bytes(""));
}
}
}
3 changes: 3 additions & 0 deletions mist-invoice-dapp/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
41 changes: 41 additions & 0 deletions mist-invoice-dapp/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
node_modules
.pnp
.pnp.js

# testing
coverage

# next.js
.next/
out/

# production
build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# yarn
.yarn

# IDE
.DS_Store
1 change: 1 addition & 0 deletions mist-invoice-dapp/.yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
34 changes: 34 additions & 0 deletions mist-invoice-dapp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
42 changes: 42 additions & 0 deletions mist-invoice-dapp/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import type { JestConfigWithTsJest } from "ts-jest";

const jestConfig: JestConfigWithTsJest = {
preset: "ts-jest",
testEnvironment: "jsdom",
coveragePathIgnorePatterns: [
"node_modules",
"src/utils/constants.ts",
"src/index.tsx",
"src/reportWebVitals.ts"
],
coverageThreshold: {
global: {
branches: 50,
functions: 50,
lines: 50,
statements: 50
}
},
setupFilesAfterEnv: ["<rootDir>/jest.setup.ts"],
transform: {
"^.+\\.tsx?$": [
"ts-jest",
{
tsconfig: {
jsx: "react"
} //"tsconfig.test.json"
}
],
"node_modules/preact/.+\\.(j|t)sx?$": [
"ts-jest",
{
tsconfig: {
jsx: "react"
}
}
]
},
transformIgnorePatterns: ["node_modules/(?!preact)"]
};

export default jestConfig;
Loading