Skip to content

Commit

Permalink
Merge pull request #91 from CirclesUBI/20240207-circles-issuance-and-…
Browse files Browse the repository at this point in the history
…demurrage

Issuance, demurrage, personal mint
  • Loading branch information
jaensen authored Feb 8, 2024
2 parents 1d8e333 + c72c75e commit 42fb4ab
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 70 deletions.
26 changes: 25 additions & 1 deletion src/circles/Circles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,28 @@ pragma solidity >=0.8.13;
import "openzeppelin-contracts/contracts/token/ERC1155/ERC1155.sol";
import "openzeppelin-contracts/contracts/token/ERC1155/utils/ERC1155Holder.sol";

abstract contract Circles is ERC1155 {}
contract Circles is ERC1155 {
// Constants

/**
* @dev ERC1155 tokens MUST be 18 decimals. Used to calculate the issuance rate.
*/
uint8 public constant DECIMALS = uint8(18);

/**
* @notice Issue one Circle per hour for each human.
*/
uint256 public constant ISSUANCE_PERIOD = 1 hours;

/**
* @notice Upon claiming, the maximum claim is upto two weeks
* of history. Unclaimed older Circles are unclaimable.
*/
uint256 public constant MAX_CLAIM_DURATION = 2 weeks;

// Constructor

constructor(string memory uri_) ERC1155(uri_) {}

// External functions
}
167 changes: 98 additions & 69 deletions src/hub/Hub.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,9 @@ contract Hub is Circles {
address public constant CIRCLES_STOPPED_V1 = address(0x1);

/**
* @notice Indefinitely, or approximate future infinity with uint96.max
* @notice Indefinite future, or approximated with uint96.max
*/
uint96 public constant INDEFINITELY = type(uint96).max;
uint96 public constant INDEFINITE_FUTURE = type(uint96).max;

// State variables

Expand Down Expand Up @@ -124,8 +124,6 @@ contract Hub is Circles {
*/
mapping(address => MintTime) public mintTimes;

mapping(address => bool) public stopped;

mapping(uint256 => WrappedERC20) public tokenIDToInfERC20;

// Mint policy registered by avatar.
Expand Down Expand Up @@ -182,7 +180,7 @@ contract Hub is Circles {
* (todo: eg. "https://fallback.aboutcircles.com/v1/circles/{id}.json")
*/
constructor(IHubV1 _hubV1, address _standardTreasury, uint256 _bootstrapTime, string memory _fallbackUri)
ERC1155(_fallbackUri)
Circles(_fallbackUri)
{
require(address(_hubV1) != address(0), "Hub v1 address can not be zero.");
require(_standardTreasury != address(0), "Standard treasury address can not be zero.");
Expand Down Expand Up @@ -212,7 +210,7 @@ contract Hub is Circles {
function registerHuman(bytes32 _cidV0Digest) external duringBootstrap {
// only available for v1 users with stopped v1 mint, for initial bootstrap period
address v1CirclesStatus = _registerHuman(msg.sender);
require(v1CirclesStatus != CIRCLES_STOPPED_V1, "Avatar must have stopped v1 Circles contract.");
require(v1CirclesStatus == CIRCLES_STOPPED_V1, "Avatar must have stopped v1 Circles contract.");

// store the IPFS CIDv0 digest for the avatar metadata
tokenIdToCidV0Digest[_toTokenId(msg.sender)] = _cidV0Digest;
Expand Down Expand Up @@ -242,7 +240,7 @@ contract Hub is Circles {
_mint(_human, _toTokenId(_human), WELCOME_BONUS, "");

// set trust to indefinite future, but avatar can edit this later
_trust(msg.sender, _human, INDEFINITELY);
_trust(msg.sender, _human, INDEFINITE_FUTURE);

emit InviteHuman(msg.sender, _human);
}
Expand Down Expand Up @@ -320,6 +318,11 @@ contract Hub is Circles {
emit CidV0(msg.sender, _cidV0Digest);
}

/**
* Register organization allows to register an organization avatar.
* @param _name name of the organization
* @param _cidV0Digest IPFS CIDv0 digest for the organization metadata
*/
function registerOrganization(string calldata _name, bytes32 _cidV0Digest) external {
require(_isValidName(_name), "Invalid organization name.");
_insertAvatar(msg.sender);
Expand Down Expand Up @@ -387,6 +390,20 @@ contract Hub is Circles {
_mint(msg.sender, _toTokenId(_group), sumAmounts, "");
}

function stop() external {
require(isHuman(msg.sender), "Only human can call stop.");
MintTime storage mintTime = mintTimes[msg.sender];
// stop future mints of personal Circles
// by setting the last mint time to indefinite future.
mintTime.lastMintTime = INDEFINITE_FUTURE;
}

function stopped(address _human) external view returns (bool) {
require(isHuman(_human), "Only personal Circles can stopped or not stopped.");
MintTime storage mintTime = mintTimes[msg.sender];
return (mintTime.lastMintTime == INDEFINITE_FUTURE);
}

// check if path transfer can be fully ERC1155 compatible
// note: matrix math needs to consider mints, otherwise it won't add up

Expand Down Expand Up @@ -469,14 +486,26 @@ contract Hub is Circles {

// Public functions

/**
* Checks if an avatar is registered as a human.
* @param _human address of the human to check
*/
function isHuman(address _human) public view returns (bool) {
return mintTimes[_human].lastMintTime > 0;
}

/**
* Checks if an avatar is registered as a group.
* @param _group address of the group to check
*/
function isGroup(address _group) public view returns (bool) {
return mintPolicies[_group] != address(0);
}

/**
* Checks if an avatar is registered as an organization.
* @param _organization address of the organization to check
*/
function isOrganization(address _organization) public view returns (bool) {
return avatars[_organization] != address(0) && mintPolicies[_organization] == address(0)
&& mintTimes[_organization].lastMintTime == uint256(0);
Expand All @@ -494,6 +523,67 @@ contract Hub is Circles {
return super.uri(_id);
}

/**
* @dev checks whether string is a valid name by checking
* the length as max 32 bytes and the allowed characters: 0-9, A-Z, a-z, space,
* hyphen, underscore, period, parentheses, apostrophe,
* ampersand, plus and hash.
* This restricts the contract name to a subset of ASCII characters,
* and excludes unicode characters for other alphabets and emoticons.
* Instead the default ERC1155 metadata read from the IPFS CID registry,
* should provide the full display name with unicode characters.
* Names are not checked for uniqueness.
*/
function _isValidName(string memory _name) public pure returns (bool) {
bytes memory nameBytes = bytes(_name);
if (nameBytes.length > 32 || nameBytes.length == 0) return false; // Check length

for (uint256 i = 0; i < nameBytes.length; i++) {
bytes1 char = nameBytes[i];
if (
!(char >= 0x30 && char <= 0x39) // 0-9
&& !(char >= 0x41 && char <= 0x5A) // A-Z
&& !(char >= 0x61 && char <= 0x7A) // a-z
&& !(char == 0x20) // Space
&& !(char == 0x2D || char == 0x5F) // Hyphen (-), Underscore (_)
&& !(char == 0x2E) // Period (.)
&& !(char == 0x28 || char == 0x29) // Parentheses ( () )
&& !(char == 0x27) // Apostrophe (')
&& !(char == 0x26) // Ampersand (&)
&& !(char == 0x2B || char == 0x23) // Plus (+), Hash (#)
) {
return false;
}
}
return true;
}

/**
* @dev checks whether string is a valid symbol by checking
* the length as max 16 bytes and the allowed characters: 0-9, A-Z, a-z,
* hyphen, underscore.
*/
function _isValidSymbol(string memory _symbol) public pure returns (bool) {
bytes memory symbolBytes = bytes(_symbol);
if (symbolBytes.length == 0 || symbolBytes.length > 16) {
return false; // Check length is within range
}

for (uint256 i = 0; i < symbolBytes.length; i++) {
bytes1 char = symbolBytes[i];
if (
// allowed ASCII characters 0-9, A-Z, a-z, Hyphen (-), Underscore (_)
!(
(char >= 0x30 && char <= 0x39) || (char >= 0x41 && char <= 0x5A) || (char >= 0x61 && char <= 0x7A)
|| (char == 0x2D) || (char == 0x5F)
)
) {
return false;
}
}
return true;
}

// Internal functions

/**
Expand All @@ -514,7 +604,7 @@ contract Hub is Circles {
mintTime.lastMintTime = uint96(block.timestamp);

// trust self indefinitely, cannot be altered later
_trust(_human, _human, INDEFINITELY);
_trust(_human, _human, INDEFINITE_FUTURE);

return v1CirclesStatus;
}
Expand Down Expand Up @@ -605,67 +695,6 @@ contract Hub is Circles {

// Private functions

/**
* @dev checks whether string is a valid name by checking
* the length as max 32 bytes and the allowed characters: 0-9, A-Z, a-z, space,
* hyphen, underscore, period, parentheses, apostrophe,
* ampersand, plus and hash.
* This restricts the contract name to a subset of ASCII characters,
* and excludes unicode characters for other alphabets and emoticons.
* Instead the default ERC1155 metadata read from the IPFS CID registry,
* should provide the full display name with unicode characters.
* Names are not checked for uniqueness.
*/
function _isValidName(string memory _name) public pure returns (bool) {
bytes memory nameBytes = bytes(_name);
if (nameBytes.length > 32 || nameBytes.length == 0) return false; // Check length

for (uint256 i = 0; i < nameBytes.length; i++) {
bytes1 char = nameBytes[i];
if (
!(char >= 0x30 && char <= 0x39) // 0-9
&& !(char >= 0x41 && char <= 0x5A) // A-Z
&& !(char >= 0x61 && char <= 0x7A) // a-z
&& !(char == 0x20) // Space
&& !(char == 0x2D || char == 0x5F) // Hyphen (-), Underscore (_)
&& !(char == 0x2E) // Period (.)
&& !(char == 0x28 || char == 0x29) // Parentheses ( () )
&& !(char == 0x27) // Apostrophe (')
&& !(char == 0x26) // Ampersand (&)
&& !(char == 0x2B || char == 0x23) // Plus (+), Hash (#)
) {
return false;
}
}
return true;
}

/**
* @dev checks whether string is a valid symbol by checking
* the length as max 16 bytes and the allowed characters: 0-9, A-Z, a-z,
* hyphen, underscore.
*/
function _isValidSymbol(string memory _symbol) public pure returns (bool) {
bytes memory symbolBytes = bytes(_symbol);
if (symbolBytes.length == 0 || symbolBytes.length > 16) {
return false; // Check length is within range
}

for (uint256 i = 0; i < symbolBytes.length; i++) {
bytes1 char = symbolBytes[i];
if (
// allowed ASCII characters 0-9, A-Z, a-z, Hyphen (-), Underscore (_)
!(
(char >= 0x30 && char <= 0x39) || (char >= 0x41 && char <= 0x5A) || (char >= 0x61 && char <= 0x7A)
|| (char == 0x2D) || (char == 0x5F)
)
) {
return false;
}
}
return true;
}

/**
* @dev Internal function to upsert a trust marker for a truster and a trusted address.
* It will initialize the linked list for the truster if it does not exist yet.
Expand Down

0 comments on commit 42fb4ab

Please sign in to comment.