Skip to content

Commit

Permalink
Merge pull request #10 from TogetherCrew/eas-resolver
Browse files Browse the repository at this point in the history
Resolver + Permissions + ApplicationManager improvements
cyri113 authored Dec 5, 2024
2 parents 62c51fc + 8dc4625 commit d1be417
Showing 43 changed files with 347,526 additions and 6,392 deletions.
Binary file modified .yarn/install-state.gz
Binary file not shown.
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -11,3 +11,81 @@ REPORT_GAS=true npx hardhat test
npx hardhat node
npx hardhat ignition deploy ./ignition/modules/Lock.ts
```

## Addresses

| Address | Tag |
| ------------------------------------------ | ------------------------ |
| 0x548c887A24077e13F17a1cE21B3a24B9c06e3D8d | TogetherCrew Deployer |
| 0x1A486364AEB60Db41108799CfEfFc5dA98B40574 | TogetherCrew Application |
| 0x127D8ed45aF416019dB1D4a39Ad44141A8FF56b2 | TogetherCrew Manager |
| 0xc2539c70de7b24b9124e4e897083ccc72e83c7c7 | TogetherCrew Attester |

| Address | Contract | Network |
| ------------------------------------------------------------------ | ------------------ | ---------------- |
| 0x1991D39FAF168EC48BaF10bbec8A8b6751BfC1E2 | AccessManager | Arbitrum |
| 0x4B015ed3A1C7244544e00aC947077593D6789F74 | OIDResolver | Arbitrum |
| 0x8006cCF2b3240bB716c86E5a16A9dD9b32eC5c53 | ApplicationManager | Arbitrum |
| 0x9a85Bb58CFb60ABd205c4Af7039fF73C86b41bd8 | PermissionManager | Arbitrum |
| 0x6b5b50f2de8b387664838bd3c751e21f6b9aac7cf4bf5b2fb86e760b89a8a22d | Eas Schema | Arbitrum |
| 0x8194157B9464683E552c810b4FEA66251435606b | AccessManager | Base Sepolia |
| 0x79558DE98808e053442f34A8834cd1f645561CE4 | OIDResolver | Base Sepolia |
| 0xF65e300B0e622B1Bc224c7351397ea2FF29f1c3D | ApplicationManager | Base Sepolia |
| 0x52d0a71B42Dd84532A7B332fdfa059E8a7391092 | PermissionManager | Base Sepolia |
| 0xe8c59f8de4cdf61c8ebefa3ed83d714acc767dda3bbff00623e73f5a8bf5255f | Eas Schema | Base Sepolia |
| 0x07d53fDeAb271f25648D4c1f600D267C87be608a | AccessManager | Optimism Sepolia |
| 0xf304B86273d2A1BB62Fbf1292481496b3cf04572 | OIDResolver | Optimism Sepolia |
| 0xb250C2b5967FEc8241FD9a26C30145Fbdf347eEC | ApplicationManager | Optimism Sepolia |
| 0xFcE488b93696Ec5e279b8257E67F074AbFEc59d8 | PermissionManager | Optimism Sepolia |
| 0x2c988095892ea57c600e5cc6fb62531502bc0c8d038ac39dc3fab161b6f122db | Eas Schema | Optimism Sepolia |

## Deployments

Before deploying, ensure that previous deployment files are removed to prevent conflicts:

- **Remove the directory**: `ignition/deployments/chain-{chainId}` (replace `{chainId}` with the ID of the chain you're deploying to).

### Deploying to Localhost

1. **Start the Hardhat local node**:

- `npx hardhat node`

- **Deploy contracts to localhost**:

1. `npx hardhat run ./scripts/deploys/deploy.ts --network localhost`

### Deploying to a Network

1. **Set up environment variables**:

- **Private Key**: Set your wallet's private key as `PRIVATE_KEY`.
- **Block Explorer API Key**: Set your block explorer API key (e.g., Etherscan API key) for contract verification.

Use Hardhat's `vars` command to set and get environment variables:

- `npx hardhat vars set PRIVATE_KEY
npx hardhat vars get PRIVATE_KEY`
- **Update Hardhat configuration**:

- In your `hardhat.config.js` or `hardhat.config.ts` file:
- **Add network configuration** under `networks` with the appropriate settings (e.g., RPC URL, accounts).
- **Configure Etherscan** for contract verification by adding your API key under `etherscan`.

- **Create a deployment script**:

- Place your deployment script in the `scripts/deploys/` directory.

- **Provide necessary addresses**:

- **Wallet Addresses**: Ensure your deployment script has access to the necessary wallet addresses.
- **EAS Contract Address**: Include the EAS (Ethereum Attestation Service) contract address.

Refer to the [EAS Contracts Installation Guide](https://docs.attest.org/docs/quick--start/contracts#installation) for details.

- **Deploy contracts to the network**:

`npx hardhat run ./scripts/deploys/{scriptname}.ts --network {networkname}`

- Replace `{scriptname}` with your deployment script name.
- Replace `{networkname}` with the network name as defined in your `hardhat.config`.
3 changes: 2 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -16,7 +16,8 @@
"./cache",
"./node_modules",
"./coverage",
"./coverage.json"
"./coverage.json",
"./ignition/deployments"
]
}
}
49 changes: 32 additions & 17 deletions contracts/ApplicationManager.sol
Original file line number Diff line number Diff line change
@@ -2,13 +2,19 @@
pragma solidity 0.8.26;

import {IApplicationManager} from "./IApplicationManager.sol";
import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract ApplicationManager is IApplicationManager {
contract ApplicationManager is
IApplicationManager,
AccessManaged,
ReentrancyGuard
{
mapping(uint => Application) private applications;
mapping(address => bool) private addressUsed;
uint private nextApplicationId;

constructor() {}
constructor(address initialAuthority) AccessManaged(initialAuthority) {}

function applicationExists(uint id) internal view returns (bool) {
return applications[id].account != address(0);
@@ -18,35 +24,44 @@ contract ApplicationManager is IApplicationManager {
return nextApplicationId;
}

function createApplication(Application memory newApplication) external {
function createApplication(
ApplicationDto memory dto
) external nonReentrant restricted {
require(
!addressUsed[newApplication.account],
!addressUsed[dto.account],
"Address already used for another application"
);
applications[nextApplicationId] = newApplication;
addressUsed[newApplication.account] = true;
emit ApplicationCreated(
nextApplicationId,
applications[nextApplicationId]
);
uint id = nextApplicationId;
nextApplicationId++;
Application memory newApplication = Application({
id: id,
name: dto.name,
account: dto.account
});
applications[id] = newApplication;
addressUsed[newApplication.account] = true;
emit ApplicationCreated(id, applications[id]);
}

function updateApplication(
uint id,
Application memory updatedApplication
) external {
ApplicationDto memory dto
) external nonReentrant restricted {
require(applicationExists(id), "Application does not exist");
require(
!addressUsed[updatedApplication.account] ||
applications[id].account == updatedApplication.account,
"Address already used for another application"
!addressUsed[dto.account] ||
applications[id].account == dto.account,
"Account used by another application"
);
applications[id] = updatedApplication;
applications[id] = Application({
id: id,
name: dto.name,
account: dto.account
});
emit ApplicationUpdated(id, applications[id]);
}

function deleteApplication(uint id) external {
function deleteApplication(uint id) external nonReentrant restricted {
require(applicationExists(id), "Application does not exist");
addressUsed[applications[id].account] = false;
emit ApplicationDeleted(id, applications[id]);
20 changes: 17 additions & 3 deletions contracts/IApplicationManager.sol
Original file line number Diff line number Diff line change
@@ -2,7 +2,13 @@
pragma solidity 0.8.26;

interface IApplicationManager {
struct ApplicationDto {
string name;
address account;
}

struct Application {
uint id;
string name;
address account;
}
@@ -12,9 +18,17 @@ interface IApplicationManager {
event ApplicationDeleted(uint id, Application application);

function getNextApplicationId() external view returns (uint);
function createApplication(Application memory application) external;
function updateApplication(uint id, Application memory application) external;

function createApplication(ApplicationDto memory dto) external;

function updateApplication(uint id, ApplicationDto memory dto) external;

function deleteApplication(uint id) external;

function getApplication(uint id) external view returns (Application memory);
function getApplications(uint start, uint limit) external returns (Application[] memory);

function getApplications(
uint start,
uint limit
) external returns (Application[] memory);
}
12 changes: 12 additions & 0 deletions contracts/IOIDPermissionManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.26;

interface IOIDPermissionManager {
event PermissionUpdated(bytes32 key, address account, bool granted);
function grantPermission(bytes32 attestation_uid, address account) external;
function revokePermission(bytes32 attestation_uid, address account) external;
function hasPermission(
bytes32 key,
address account
) external view returns (bool);
}
6 changes: 6 additions & 0 deletions contracts/Imports.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.26;

import "@ethereum-attestation-service/eas-contracts/contracts/SchemaRegistry.sol";
import "@ethereum-attestation-service/eas-contracts/contracts/EAS.sol";
import "@ethereum-attestation-service/eas-contracts/contracts/eip712/proxy/EIP712Proxy.sol";
15 changes: 15 additions & 0 deletions contracts/OIDAccessManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.26;

// solhint-disable-next-line max-line-length
import {AccessManagerUpgradeable} from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagerUpgradeable.sol";

contract OIDAccessManager is AccessManagerUpgradeable {
uint64 public constant APPLICATION_MANAGER_ROLE = 1;
uint64 public constant ATTESTATION_MANAGER_ROLE = 2;
uint64 public constant PERMISSION_MANAGER_ROLE = 3;

function initialize() public initializer {
__AccessManager_init(msg.sender);
}
}
123 changes: 123 additions & 0 deletions contracts/OIDPermissionManager.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.26;
import {IOIDPermissionManager} from "./IOIDPermissionManager.sol";
import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol";
import {IEAS} from "@ethereum-attestation-service/eas-contracts/contracts/IEAS.sol";
import {Attestation} from "@ethereum-attestation-service/eas-contracts/contracts/Common.sol";
import {IAccessManager} from "@openzeppelin/contracts/access/manager/IAccessManager.sol";
import {OIDAccessManager} from "./OIDAccessManager.sol";

contract OIDPermissionManager is IOIDPermissionManager, AccessManaged {
error UnauthorizedAccess(address caller);
error AttestationNotFound(bytes32 attestation_uid);
error AttestationRevoked(bytes32 attestation_uid);

struct Permission {
bool granted;
bytes32 attestation_uid;
}
IEAS internal immutable _eas;

mapping(bytes32 => mapping(address => Permission)) private permissions;

constructor(
address initialAuthority,
IEAS initialEAS
) AccessManaged(initialAuthority) {
_eas = initialEAS;
}


function grantPermission(bytes32 attestation_uid, address account) external {
_updatePermission(attestation_uid, account, true);
}


function revokePermission(bytes32 attestation_uid, address account) external override {
_updatePermission(attestation_uid, account, false);
}

function hasPermission(bytes32 key,address account) external view override returns (bool) {
Permission storage permission = permissions[key][account];
if (!_permissionExists(permission) || !permission.granted) {
return false;
}
Attestation memory attestation = _getAttestation(permission.attestation_uid);
if (_isAttestationRevoked(attestation)) {
return false;
}
return true;
}


function _updatePermission(bytes32 attestation_uid, address account, bool granted) internal {
Attestation memory attestation = _getAttestation(attestation_uid);
if (_isAttestationRevoked(attestation)) {
revert AttestationRevoked(attestation_uid);
}
_checkAuthorization(attestation);
bytes32 key = _decodeAttestationKey(attestation);
_setPermission(key, account, granted, attestation_uid);
emit PermissionUpdated(key, account, granted);
}

function _checkAuthorization(Attestation memory attestation) internal view {
if (!_isAttestationRecipient(attestation) && !_isPermissionManager()) {
revert UnauthorizedAccess(msg.sender);
}
}


function _isPermissionManager() internal view returns (bool) {
OIDAccessManager access = OIDAccessManager(authority());
(bool isMember, ) = access.hasRole(
access.PERMISSION_MANAGER_ROLE(),
msg.sender
);
return isMember;
}


function _permissionExists(Permission storage permission) internal view returns (bool) {
return permission.attestation_uid != bytes32(0);
}

function _setPermission(
bytes32 key,
address account,
bool granted,
bytes32 attestation_uid
) internal {
permissions[key][account] = Permission({
granted: granted,
attestation_uid: attestation_uid
});
}

function _getAttestation(bytes32 attestation_uid) private view returns (Attestation memory) {
Attestation memory attestation = _eas.getAttestation(attestation_uid);
if (attestation.uid == bytes32(0)) {
revert AttestationNotFound(attestation_uid);
}
return attestation;
}

function _isAttestationRecipient(Attestation memory attestation) internal view returns (bool) {
return attestation.recipient == msg.sender;
}

function _isAttestationRevoked(Attestation memory attestation) internal pure returns (bool) {
return attestation.revocationTime != 0;
}

function eas() external view returns (IEAS) {
return _eas;
}


function _decodeAttestationKey(Attestation memory attestation) internal pure returns (bytes32) {
bytes32 key = abi.decode(attestation.data, (bytes32));
return key;
}

}
Loading

0 comments on commit d1be417

Please sign in to comment.