Skip to content

Commit

Permalink
Merge pull request #3 from hats-finance/signer
Browse files Browse the repository at this point in the history
Add basic contract
  • Loading branch information
shayzluf authored Oct 1, 2023
2 parents 3c81cac + bffc794 commit da4b579
Show file tree
Hide file tree
Showing 12 changed files with 428 additions and 18 deletions.
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "onchain_audit_registry/lib/forge-std"]
path = onchain_audit_registry/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "onchain_audit_registry/lib/openzeppelin-contracts"]
path = onchain_audit_registry/lib/openzeppelin-contracts
url = https://github.com/OpenZeppelin/openzeppelin-contracts
13 changes: 4 additions & 9 deletions metamask_signer/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ const TYPES = {
{ name: "auditor", type: "Auditor" },
{ name: "issuedAt", type: "uint256" },
{ name: "ercs", type: "uint256[]" },
{ name: "contract", type: "Contract" },
{ name: "chainId", type: "uint256" },
{ name: "contractAddress", type: "address" },
{ name: "auditHash", type: "bytes32" },
{ name: "auditUri", type: "string" }
],
Expand All @@ -22,10 +23,6 @@ const TYPES = {
{ name: "uri", type: "string" },
{ name: "authors", type: "string[]" }
],
Contract: [
{ name: "chainId", type: "uint256" },
{ name: "address", type: "address" }
]
};

let authorCount = 0;
Expand Down Expand Up @@ -183,10 +180,8 @@ async function constructTypedData() {
},
issuedAt: Date.now(),
ercs: Array.from(document.querySelectorAll('.erc-input')).map(input => parseInt(input.value)),
contract: {
chainId: parseInt(document.getElementById('chainId').value),
address: document.getElementById('contractAddress').value
},
chainId: parseInt(document.getElementById('chainId').value),
contractAddress: document.getElementById('contractAddress').value,
auditHash: await computePdfHash(),
auditUri: document.getElementById('auditUri').value
}
Expand Down
34 changes: 34 additions & 0 deletions onchain_audit_registry/.github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: test

on: workflow_dispatch

env:
FOUNDRY_PROFILE: ci

jobs:
check:
strategy:
fail-fast: true

name: Foundry project
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
submodules: recursive

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Run Forge build
run: |
forge --version
forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test -vvv
id: test
14 changes: 14 additions & 0 deletions onchain_audit_registry/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Compiler files
cache/
out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Dotenv file
.env
66 changes: 66 additions & 0 deletions onchain_audit_registry/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
6 changes: 6 additions & 0 deletions onchain_audit_registry/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
1 change: 1 addition & 0 deletions onchain_audit_registry/lib/forge-std
Submodule forge-std added at 1d9650
1 change: 1 addition & 0 deletions onchain_audit_registry/lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at fd81a9
5 changes: 5 additions & 0 deletions onchain_audit_registry/remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
ds-test/=lib/forge-std/lib/ds-test/src/
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/
openzeppelin/=lib/openzeppelin-contracts/contracts/
140 changes: 140 additions & 0 deletions onchain_audit_registry/src/AuditRegistry.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {EIP712} from "openzeppelin/utils/cryptography/EIP712.sol";
import {Ownable} from "openzeppelin/access/Ownable.sol";

contract AuditRegistry is Ownable, EIP712 {
error ArraysLengthMismatched();
error BadSignatureLength();
error BadSignatureVersion();

bytes32 public constant AUDIT_SUMMARY_TYPEHASH =
keccak256("AuditSummary(Auditor auditor,uint256 issuedAt,uint256[] ercs,uint256 chainId,address contractAddress,bytes32 auditHash,string auditUri)");
bytes32 public constant AUDITOR_TYPEHASH = keccak256("Auditor(string name,string uri,string[] authors)");

struct Auditor {
string name;
string uri;
string[] authors;
}

struct AuditSummary {
Auditor auditor;
uint256 issuedAt;
uint256[] ercs;
uint256 chainId;
address contractAddress;
bytes32 auditHash;
string auditUri;
}

string public constant NAME = "ERC-7652: Onchain Audit Representation";
string public constant VERSION = "1.0";

mapping(address => bool) public auditorsWhitelist;

event AuditorsWhitelistUpdated(address[] indexed _auditors, bool[] _isWhitelisted);

constructor(address _owner) EIP712(NAME, VERSION) {
_transferOwnership(_owner);
}

function updateAuditorsWhitelist(address[] calldata _auditors, bool[] calldata _isWhitelisted) external onlyOwner {
if (_auditors.length != _isWhitelisted.length) {
revert ArraysLengthMismatched();
}

for (uint256 i = 0; i < _auditors.length;) {
auditorsWhitelist[_auditors[i]] = _isWhitelisted[i];
unchecked {
++i;
}
}

emit AuditorsWhitelistUpdated(_auditors, _isWhitelisted);
}

function verifyAuditSummary(AuditSummary calldata _auditSummary, bytes calldata _sig) external view returns(bool) {
bytes32 txHash = _getTxHash(_auditSummary);

address recoveredAddress = _recover(txHash, _sig);

return auditorsWhitelist[recoveredAddress];
}

function verifyAuditSummary(AuditSummary calldata _auditSummary, bytes32 r, bytes32 s, uint8 v) external view returns(bool) {
bytes32 txHash = _getTxHash(_auditSummary);

address recoveredAddress = ecrecover(txHash, v, r, s);

return auditorsWhitelist[recoveredAddress];
}

function _getTxHash(AuditSummary calldata _auditSummary) internal view returns (bytes32) {
bytes32 structHash = keccak256(abi.encode(
AUDIT_SUMMARY_TYPEHASH,
_hashAuditor(_auditSummary.auditor),
_auditSummary.issuedAt,
keccak256(abi.encodePacked(_auditSummary.ercs)),
_auditSummary.chainId,
_auditSummary.contractAddress,
_auditSummary.auditHash,
keccak256(bytes(_auditSummary.auditUri))
));

return _hashTypedDataV4(structHash);
}

function _hashAuditor(Auditor calldata _auditor) internal pure returns (bytes32) {
bytes32[] memory authors = new bytes32[](_auditor.authors.length);
for (uint256 i = 0; i < _auditor.authors.length; ++i) {
authors[i] = keccak256(bytes(_auditor.authors[i]));
}
return keccak256(abi.encode(
AUDITOR_TYPEHASH,
keccak256(bytes(_auditor.name)),
keccak256(bytes(_auditor.uri)),
keccak256(abi.encodePacked(authors))
));
}

function _recover(
bytes32 hash,
bytes memory signature
) internal pure returns (address) {
bytes32 r;
bytes32 s;
uint8 v;

// Check the signature length
if (signature.length != 65) {
revert BadSignatureLength();
}

// Divide the signature in r, s and v variables
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}

// Version of signature should be 27 or 28, but 0 and 1 are also possible versions
if (v < 27) {
v += 27;
}

// If the version is correct return the signer address
if (v != 27 && v != 28) {
revert BadSignatureVersion();
} else {
return ecrecover(hash, v, r, s);
}
}

// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view virtual returns (bytes32) {
return _domainSeparatorV4();
}

}
Loading

0 comments on commit da4b579

Please sign in to comment.