Skip to content

Commit

Permalink
Chore/lets merge together (#1220)
Browse files Browse the repository at this point in the history
* [Prod]:Deployed to Celo, updated Defender, version bumps for everyone (#1137)

* Fix/package build (#1124)

* chore(npm): sdk to workspace to stop link:
* chore(npm): contracts in workspace, no link:
* chore(bump): 0.8.7
* chore(bump): contracts 0.8.7 into sdk 0.8.8
* fix(ci): linting errors from updated ws
* fix(doc): optimism chainId to 10 from 100
* chore(build): remove type module from package.json
* chore(contracts): package sol files
* chore(pack): add sol files to contracts package
* chore(bump): contract 0.8.9
* chore(pack): exports setup
* chore(pack): contracts dir in package
* chore(pack): cleanup build scripts
* chore(bump): contracts 0.8.10
* chore(pack): add all contracts for inheritence
* chore(bump): contracts 0.8.11
* chore(bump): sdk 0.8.10 with updated contracts package
* chore(bump): updated packages in frontend
* chore(gha): run action on pr and push to develop

* Feat/deploy to celo (#1135)

* Add HC contracts to OpenZeppelin Defender (#1134)

* Fix/package build (#1124)

* chore(npm): sdk to workspace to stop link:
* chore(npm): contracts in workspace, no link:
* chore(bump): 0.8.7
* chore(bump): contracts 0.8.7 into sdk 0.8.8
* fix(ci): linting errors from updated ws
* fix(doc): optimism chainId to 10 from 100
* chore(build): remove type module from package.json
* chore(contracts): package sol files
* chore(pack): add sol files to contracts package
* chore(bump): contract 0.8.9
* chore(pack): exports setup
* chore(pack): contracts dir in package
* chore(pack): cleanup build scripts
* chore(bump): contracts 0.8.10
* chore(pack): add all contracts for inheritence
* chore(bump): contracts 0.8.11
* chore(bump): sdk 0.8.10 with updated contracts package
* chore(bump): updated packages in frontend
* chore(gha): run action on pr and push to develop

* feat(defender): use hc packages in oz defender

* feat(celo): graph deploy, sdk and defender config

* chore(bump): SDK 0.8.14 Celo deployment

* feat(celo): add celo support to frontend

* chore(graph): fix turbo test flow

* chore(graph): add comment to p.json

* fix(gha): use pnpm cache in graph gha (#1138)

* chore: add wrapper components for split and merge functionality (#1148)

* format external url in case it is a direct ipfs link (#1181)

* merge fixes

---------

Co-authored-by: bitbeckers <code@bitbeckers.com>
  • Loading branch information
Jipperism and bitbeckers authored Dec 7, 2023
1 parent 1cc481f commit 0c249aa
Show file tree
Hide file tree
Showing 9 changed files with 935 additions and 111 deletions.
70 changes: 70 additions & 0 deletions contracts/contracts/AllowlistMinter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {MerkleProofUpgradeable} from "oz-upgradeable/utils/cryptography/MerkleProofUpgradeable.sol";
import {IAllowlist} from "./interfaces/IAllowlist.sol";

import {Errors} from "./libs/Errors.sol";

/// @title Interface for hypercert token interactions
/// @author bitbeckers
/// @notice This interface declares the required functionality for a hypercert token
/// @notice This interface does not specify the underlying token type (e.g. 721 or 1155)
contract AllowlistMinter is IAllowlist {
event AllowlistCreated(uint256 tokenID, bytes32 root);
event LeafClaimed(uint256 tokenID, bytes32 leaf);

mapping(uint256 => bytes32) internal merkleRoots;
mapping(uint256 => mapping(bytes32 => bool)) public hasBeenClaimed;
mapping(uint256 => uint256) internal maxUnits;
mapping(uint256 => uint256) internal minted;

function isAllowedToClaim(bytes32[] calldata proof, uint256 claimID, bytes32 leaf)
external
view
returns (bool isAllowed)
{
if (merkleRoots[claimID].length == 0) revert Errors.DoesNotExist();
isAllowed = MerkleProofUpgradeable.verifyCalldata(proof, merkleRoots[claimID], leaf);
}

function _createAllowlist(uint256 claimID, bytes32 merkleRoot, uint256 units) internal {
if (merkleRoot == "" || units == 0) revert Errors.Invalid();
if (merkleRoots[claimID] != "") revert Errors.DuplicateEntry();

merkleRoots[claimID] = merkleRoot;
maxUnits[claimID] = units;
emit AllowlistCreated(claimID, merkleRoot);
}

function _processClaim(bytes32[] calldata proof, uint256 claimID, uint256 amount) internal {
if (merkleRoots[claimID].length == 0) revert Errors.DoesNotExist();

bytes32 leaf = _calculateLeaf(msg.sender, amount);

if (hasBeenClaimed[claimID][leaf]) revert Errors.AlreadyClaimed();
if (
!MerkleProofUpgradeable.verifyCalldata(proof, merkleRoots[claimID], leaf)
|| (minted[claimID] + amount) > maxUnits[claimID]
) revert Errors.Invalid();
hasBeenClaimed[claimID][leaf] = true;

emit LeafClaimed(claimID, leaf);
}

function _calculateLeaf(address account, uint256 amount) internal pure returns (bytes32 leaf) {
leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));
}

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
* Assuming 30 available slots (slots cost space, cost gas)
* 1. merkleRoots
* 2. hasBeenClaimed
* 3. maxUnits
* 4. minted
*/
uint256[26] private __gap;
}
230 changes: 230 additions & 0 deletions contracts/contracts/HypercertMinter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.16;

import {IHypercertToken} from "./interfaces/IHypercertToken.sol";
import {SemiFungible1155} from "./SemiFungible1155.sol";
import {AllowlistMinter} from "./AllowlistMinter.sol";
import {PausableUpgradeable} from "oz-upgradeable/security/PausableUpgradeable.sol";

import {Errors} from "./libs/Errors.sol";

/// @title Contract for managing hypercert claims and whitelists
/// @author bitbeckers
/// @notice Implementation of the HypercertTokenInterface using { SemiFungible1155 } as underlying token.
/// @notice This contract supports whitelisted minting via { AllowlistMinter }.
/// @dev Wrapper contract to expose and chain functions.
contract HypercertMinter is IHypercertToken, SemiFungible1155, AllowlistMinter, PausableUpgradeable {
// solhint-disable-next-line const-name-snakecase
string public constant name = "HypercertMinter";
/// @dev from typeID to a transfer policy
mapping(uint256 => TransferRestrictions) internal typeRestrictions;

/// INIT

/// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol }
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol }
function initialize() public virtual initializer {
__SemiFungible1155_init();
__Pausable_init();
}

/// EXTERNAL

/// @notice Mint a semi-fungible token for the impact claim referenced via `uri`
/// @dev see {IHypercertToken}
function mintClaim(address account, uint256 units, string memory _uri, TransferRestrictions restrictions)
external
override
whenNotPaused
{
// This enables us to release this restriction in the future
if (msg.sender != account) revert Errors.NotAllowed();
uint256 claimID = _mintNewTypeWithToken(account, units, _uri);
typeRestrictions[claimID] = restrictions;
emit ClaimStored(claimID, _uri, units);
}

/// @notice Mint semi-fungible tokens for the impact claim referenced via `uri`
/// @dev see {IHypercertToken}
function mintClaimWithFractions(
address account,
uint256 units,
uint256[] calldata fractions,
string memory _uri,
TransferRestrictions restrictions
) external override whenNotPaused {
// This enables us to release this restriction in the future
if (msg.sender != account) revert Errors.NotAllowed();
//Using sum to compare units and fractions (sanity check)
if (_getSum(fractions) != units) revert Errors.Invalid();

uint256 claimID = _mintNewTypeWithTokens(account, fractions, _uri);
typeRestrictions[claimID] = restrictions;
emit ClaimStored(claimID, _uri, units);
}

/// @notice Mint a semi-fungible token representing a fraction of the claim
/// @dev Calls AllowlistMinter to verify `proof`.
/// @dev Mints the `amount` of units for the hypercert stored under `claimID`
function mintClaimFromAllowlist(address account, bytes32[] calldata proof, uint256 claimID, uint256 units)
external
whenNotPaused
{
_processClaim(proof, claimID, units);
_mintToken(account, claimID, units);
}

/// @notice Mint semi-fungible tokens representing a fraction of the claims in `claimIDs`
/// @dev Calls AllowlistMinter to verify `proofs`.
/// @dev Mints the `amount` of units for the hypercert stored under `claimIDs`
function batchMintClaimsFromAllowlists(
address account,
bytes32[][] calldata proofs,
uint256[] calldata claimIDs,
uint256[] calldata units
) external whenNotPaused {
uint256 len = claimIDs.length;
for (uint256 i; i < len;) {
_processClaim(proofs[i], claimIDs[i], units[i]);
unchecked {
++i;
}
}
_batchMintTokens(account, claimIDs, units);
}

/// @notice Register a claim and the whitelist for minting token(s) belonging to that claim
/// @dev Calls SemiFungible1155 to store the claim referenced in `uri` with amount of `units`
/// @dev Calls AllowlistMinter to store the `merkleRoot` as proof to authorize claims
function createAllowlist(
address account,
uint256 units,
bytes32 merkleRoot,
string memory _uri,
TransferRestrictions restrictions
) external whenNotPaused {
uint256 claimID = _createTokenType(account, units, _uri);
_createAllowlist(claimID, merkleRoot, units);
typeRestrictions[claimID] = restrictions;
emit ClaimStored(claimID, _uri, units);
}

/// @notice Split a claimtokens value into parts with summed value equal to the original
/// @dev see {IHypercertToken}
function splitFraction(address _account, uint256 _tokenID, uint256[] calldata _newFractions)
external
whenNotPaused
{
_splitTokenUnits(_account, _tokenID, _newFractions);
}

/// @notice Merge the value of tokens belonging to the same claim
/// @dev see {IHypercertToken}
function mergeFractions(address _account, uint256[] calldata _fractionIDs) external whenNotPaused {
_mergeTokensUnits(_account, _fractionIDs);
}

/// @notice Burn a claimtoken
/// @dev see {IHypercertToken}
function burnFraction(address _account, uint256 _tokenID) external whenNotPaused {
_burnToken(_account, _tokenID);
}

/// @dev see {IHypercertToken}
function unitsOf(uint256 tokenID) external view override returns (uint256 units) {
units = _unitsOf(tokenID);
}

/// @dev see {IHypercertToken}
function unitsOf(address account, uint256 tokenID) external view override returns (uint256 units) {
units = _unitsOf(account, tokenID);
}

/// PAUSABLE

function pause() external onlyOwner {
_pause();
}

function unpause() external onlyOwner {
_unpause();
}

/// METADATA

/// @dev see { IHypercertMetadata}
function uri(uint256 tokenID)
public
view
override(IHypercertToken, SemiFungible1155)
returns (string memory _uri)
{
_uri = SemiFungible1155.uri(tokenID);
}

/// TRANSFER RESTRICTIONS

function readTransferRestriction(uint256 tokenID) external view returns (string memory) {
TransferRestrictions temp = typeRestrictions[getBaseType(tokenID)];
if (temp == TransferRestrictions.AllowAll) return "AllowAll";
if (temp == TransferRestrictions.DisallowAll) return "DisallowAll";
if (temp == TransferRestrictions.FromCreatorOnly) return "FromCreatorOnly";
return "";
}

/// INTERNAL

/// @dev see { openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol }
function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {
// solhint-disable-previous-line no-empty-blocks
}

function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual override {
super._beforeTokenTransfer(operator, from, to, ids, amounts, data);

// By-pass transfer restrictions for minting and burning
if (from == address(0)) {
// Minting
return;
} else if (to == address(0)) {
// Burning
return;
}

// Transfer case, where to and from are non-zero
uint256 len = ids.length;
for (uint256 i; i < len;) {
uint256 typeID = getBaseType(ids[i]);
TransferRestrictions policy = typeRestrictions[typeID];
if (policy == TransferRestrictions.DisallowAll) {
revert Errors.TransfersNotAllowed();
} else if (policy == TransferRestrictions.FromCreatorOnly && from != creators[typeID]) {
revert Errors.TransfersNotAllowed();
}
unchecked {
++i;
}
}
}

/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
* Assuming 30 available slots (slots cost space, cost gas)
* 1. typeRestrictions
*/
uint256[29] private __gap;
}
Loading

0 comments on commit 0c249aa

Please sign in to comment.