Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save GenericMage/61873899037beb256346deabb2a5f572 to your computer and use it in GitHub Desktop.
Save GenericMage/61873899037beb256346deabb2a5f572 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.2+commit.661d1103.js&optimize=true&runs=200&gist=
// File: Contracts/imports/IERC20.sol
pragma solidity ^0.8.1;
interface IERC20 {
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint256); // Added for OMFAgent prepListing
function balanceOf(address account) external view returns (uint256);
function allowance(address owner, address spender) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
}
// File: Contracts/immateriumChapter.sol
pragma solidity ^0.8.1;
/*
* immateriumChapter.sol
* version 0.1.0:
* - Added billAndSet function to bill fees and set old ownKeys for a hearer, updating ownCycle to current chapterCycle.
* - Added getOldOwnKey function to query old ownKeys by hearer and ownCycle.
* - Added hearerCycleToOwnKey mapping for efficient old ownKey queries.
* - Modified hear function to not bill fees upfront, only adding hearer to hearers and chapterMapper.
* - Updated IimmateriumChapter interface to include new billAndSet and getOldOwnKey functions.
*/
interface IChapterMapper {
function addChapter(address hearer, address chapter) external;
function removeChapter(address hearer, address chapter) external;
function getHearerChapters(address hearer) external view returns (address[] memory);
function isHearerSubscribed(address hearer, address chapter) external view returns (bool);
function addName(string calldata name, address chapter) external;
function queryPartialName(string calldata query) external view returns (address[] memory, string[] memory);
function queryExactName(string calldata name) external view returns (address, string memory);
}
interface IimmateriumChapter {
function billFee(uint256 cell) external;
function hear() external;
function silence() external;
function luminate(string calldata dataEntry) external;
function reElect(address newElect) external;
function changeFee(uint256 newFee) external;
function setElect(address elect) external;
function setFeeInterval(uint256 interval) external;
function setChapterFee(uint256 fee) external;
function setChapterToken(address token) external;
function nextCycleKey(string calldata key, uint256 cellIndex, string calldata ownKeys) external;
function setChapterMapper(address mapper) external;
function addChapterImage(string calldata image) external;
function addChapterName(string calldata name) external;
function billAndSet(address hearer, string calldata cycleIndexes, string calldata ownKeys) external;
function searchHearers() external view returns (address[] memory, uint256[] memory);
function isHearer(address hearer) external view returns (address, string memory, uint256, bool);
function getLumen(uint256 index) external view returns (string memory, uint256, uint256, uint256);
function getActiveHearersCount() external view returns (uint256);
function nextFeeInSeconds() external view returns (uint256, uint256, uint256);
function getCellHeight() external view returns (uint256);
function getCellHearerCount(uint256 cellIndex) external view returns (uint256);
function getOldOwnKey(address hearer, uint256 ownCycle) external view returns (string memory);
}
contract immateriumChapter is IimmateriumChapter {
struct Hearer {
address hearerAddress;
string ownKey;
uint256 ownCycle;
bool status;
}
struct Lumen {
string dataEntry;
uint256 cycle;
uint256 index;
uint256 timestamp;
}
address public elect;
uint256 public feeInterval;
uint256 public chapterCycle;
uint256 public chapterFee;
uint256 public nextFee;
address public chapterToken;
address public chapterMapper;
uint256 public lastCycleVolume;
string public chapterName;
string public chapterImage;
uint256 public lumenHeight;
Hearer[] public hearers;
Lumen[] public lumens;
string[] public cycleKey;
mapping(string => Hearer) public oldKeys;
mapping(address => mapping(uint256 => string)) public hearerCycleToOwnKey;
bool private electSet;
bool private feeIntervalSet;
bool private chapterFeeSet;
bool private chapterTokenSet;
bool private chapterMapperSet;
event BillingFailed(address hearer, string reason);
event KeyUpdated(address hearer, string newKey);
event KeyUpdateFailed(address hearer, string reason);
modifier electOnly() {
require(msg.sender == elect, "electOnly");
_;
}
// Helper: Parse comma-separated string to uint256 array
function _parseIndexes(string memory indexes) private pure returns (uint256[] memory) {
bytes memory indexBytes = bytes(indexes);
uint256[] memory result = new uint256[](100);
uint256 count = 0;
uint256 temp = 0;
for (uint256 i = 0; i < indexBytes.length && count < 100; i++) {
if (indexBytes[i] == ",") {
result[count] = temp;
count++;
temp = 0;
} else {
temp = temp * 10 + (uint8(indexBytes[i]) - 48);
}
}
if (temp > 0 && count < 100) {
result[count] = temp;
count++;
}
uint256[] memory trimmed = new uint256[](count);
for (uint256 i = 0; i < count; i++) {
trimmed[i] = result[i];
}
return trimmed;
}
// Helper: Extract substring from bytes
function _substring(bytes memory data, uint256 start, uint256 end) private pure returns (string memory) {
require(start <= end && end <= data.length, "Invalid substring range");
uint256 len = end - start;
bytes memory result = new bytes(len);
for (uint256 i = 0; i < len; i++) {
result[i] = data[start + i];
}
return string(result);
}
// Helper: Parse comma-separated ownKeys string
function _parseOwnKeys(string memory ownKeys) private pure returns (string[] memory) {
bytes memory keyBytes = bytes(ownKeys);
string[] memory result = new string[](100);
uint256 count = 0;
uint256 start = 0;
for (uint256 i = 0; i < keyBytes.length && count < 100; i++) {
if (keyBytes[i] == ",") {
result[count] = _substring(keyBytes, start, i);
count++;
start = i + 1;
}
}
if (start < keyBytes.length && count < 100) {
result[count] = _substring(keyBytes, start, keyBytes.length);
count++;
}
string[] memory trimmed = new string[](count);
for (uint256 i = 0; i < count; i++) {
trimmed[i] = result[i];
}
return trimmed;
}
// Helper: Charge fee
function _chargeHearer(uint256 index) private returns (bool) {
Hearer storage hearer = hearers[index];
if (
hearer.status &&
IERC20(chapterToken).allowance(hearer.hearerAddress, address(this)) >= chapterFee &&
IERC20(chapterToken).balanceOf(hearer.hearerAddress) >= chapterFee
) {
try IERC20(chapterToken).transferFrom(hearer.hearerAddress, elect, chapterFee) returns (bool success) {
if (success) {
lastCycleVolume += chapterFee;
return true;
}
} catch {
emit BillingFailed(hearer.hearerAddress, "Transfer failed");
}
}
return false;
}
// Helper: Shift hearer entries to close gaps
function _shiftHearers(uint256 startIndex) private {
for (uint256 i = startIndex; i < hearers.length - 1; i++) {
hearers[i] = hearers[i + 1];
}
hearers.pop();
}
// Helper: Clean inactive hearers
function _cleanInactiveHearers() private {
uint256 i = 0;
while (i < hearers.length) {
if (!hearers[i].status) {
_shiftHearers(i);
} else {
i++;
}
}
}
// Helper: Get current timestamp
function _getCurrentTimestamp() private view returns (uint256) {
return block.timestamp;
}
// Helper: Increment lumenHeight
function _incrementLumenHeight() private {
lumenHeight = lumenHeight + 1;
}
function billFee(uint256 cell) external override electOnly {
if (nextFee != 0 && nextFee > block.timestamp) {
emit BillingFailed(address(0), "Fees not due");
} else {
lastCycleVolume = 0;
uint256 start = cell * 100;
uint256 end = start + 100 > hearers.length ? hearers.length : start + 100;
for (uint256 i = start; i < end; i++) {
if (hearers[i].status) {
_chargeHearer(i);
}
}
_cleanInactiveHearers();
nextFee = block.timestamp + feeInterval;
}
}
function hear() external override {
hearers.push(Hearer(msg.sender, "", chapterCycle, true));
if (chapterMapper != address(0)) {
IChapterMapper(chapterMapper).addChapter(msg.sender, address(this));
}
}
function silence() external override {
for (uint256 i = 0; i < hearers.length; i++) {
if (hearers[i].hearerAddress == msg.sender && hearers[i].status) {
hearers[i].status = false;
if (chapterMapper != address(0)) {
IChapterMapper(chapterMapper).removeChapter(msg.sender, address(this));
}
return;
}
}
revert("Not a hearer");
}
function luminate(string calldata dataEntry) external override electOnly {
lumens.push(Lumen(dataEntry, chapterCycle, lumens.length, _getCurrentTimestamp()));
_incrementLumenHeight();
}
function reElect(address newElect) external override electOnly {
elect = newElect;
}
function changeFee(uint256 newFee) external override electOnly {
require(nextFee == 0 || nextFee <= block.timestamp, "Fees not due");
chapterFee = newFee;
}
function setElect(address elect_) external override {
require(!electSet, "Elect already set");
elect = elect_;
electSet = true;
}
function setFeeInterval(uint256 interval) external override {
require(!feeIntervalSet, "Fee interval already set");
feeInterval = interval;
feeIntervalSet = true;
}
function setChapterFee(uint256 fee) external override {
require(!chapterFeeSet, "Chapter fee already set");
chapterFee = fee;
chapterFeeSet = true;
}
function setChapterToken(address token) external override {
require(!chapterTokenSet, "Chapter token already set");
chapterToken = token;
chapterTokenSet = true;
}
function setChapterMapper(address mapper) external override {
require(!chapterMapperSet, "Chapter mapper already set");
chapterMapper = mapper;
chapterMapperSet = true;
}
function nextCycleKey(string calldata key, uint256 cellIndex, string calldata ownKeys) external override electOnly {
if (nextFee != 0 && nextFee > block.timestamp) {
emit KeyUpdateFailed(address(0), "Fees not due");
return;
}
uint256 cellHeight = (hearers.length + 99) / 100;
if (cellIndex >= cellHeight) {
emit KeyUpdateFailed(address(0), "Invalid cell index");
return;
}
cycleKey.push(key);
chapterCycle++;
uint256 startIndex = cellIndex * 100;
uint256 endIndex = startIndex + 100 > hearers.length ? hearers.length : startIndex + 100;
string[] memory parsedKeys = _parseOwnKeys(ownKeys);
uint256 activeCount = 0;
for (uint256 i = startIndex; i < endIndex; i++) {
if (hearers[i].status) {
activeCount++;
}
}
if (parsedKeys.length != activeCount) {
emit KeyUpdateFailed(address(0), "Mismatched ownKeys count");
} else {
uint256 keyIndex = 0;
for (uint256 i = startIndex; i < endIndex && keyIndex < parsedKeys.length; i++) {
if (hearers[i].status) {
Hearer storage hearer = hearers[i];
oldKeys[hearer.ownKey] = Hearer(hearer.hearerAddress, hearer.ownKey, hearer.ownCycle, false);
hearerCycleToOwnKey[hearer.hearerAddress][hearer.ownCycle] = hearer.ownKey;
hearer.ownKey = parsedKeys[keyIndex];
hearer.ownCycle = chapterCycle;
emit KeyUpdated(hearer.hearerAddress, hearer.ownKey);
keyIndex++;
}
}
}
}
function billAndSet(address hearer, string calldata cycleIndexes, string calldata ownKeys) external override electOnly {
uint256 hearerIndex = type(uint256).max;
for (uint256 i = 0; i < hearers.length; i++) {
if (hearers[i].hearerAddress == hearer && hearers[i].status) {
hearerIndex = i;
break;
}
}
if (hearerIndex == type(uint256).max) {
emit BillingFailed(hearer, "Not an active hearer");
return;
}
if (!_chargeHearer(hearerIndex)) {
emit BillingFailed(hearer, "Fee charge failed");
return;
}
uint256[] memory parsedIndexes = _parseIndexes(cycleIndexes);
string[] memory parsedKeys = _parseOwnKeys(ownKeys);
if (parsedIndexes.length != parsedKeys.length) {
emit KeyUpdateFailed(hearer, "Mismatched indexes and keys");
return;
}
Hearer storage h = hearers[hearerIndex];
oldKeys[h.ownKey] = Hearer(h.hearerAddress, h.ownKey, h.ownCycle, false);
hearerCycleToOwnKey[h.hearerAddress][h.ownCycle] = h.ownKey;
for (uint256 i = 0; i < parsedIndexes.length; i++) {
if (parsedIndexes[i] >= chapterCycle) {
emit KeyUpdateFailed(hearer, "Invalid cycle index");
return;
}
hearerCycleToOwnKey[hearer][parsedIndexes[i]] = parsedKeys[i];
oldKeys[parsedKeys[i]] = Hearer(hearer, parsedKeys[i], parsedIndexes[i], false);
}
h.ownCycle = chapterCycle;
emit KeyUpdated(hearer, h.ownKey);
}
function addChapterImage(string calldata image) external override electOnly {
chapterImage = image;
}
function addChapterName(string calldata name) external override electOnly {
require(chapterMapper != address(0), "ChapterMapper not set");
chapterName = name;
IChapterMapper(chapterMapper).addName(name, address(this));
}
function searchHearers() external view override returns (address[] memory, uint256[] memory) {
uint256 count = 0;
for (uint256 i = 0; i < hearers.length && i < 1000; i++) {
if (
hearers[i].status &&
hearers[i].ownCycle < chapterCycle &&
IERC20(chapterToken).allowance(hearers[i].hearerAddress, address(this)) >= chapterFee &&
IERC20(chapterToken).balanceOf(hearers[i].hearerAddress) >= chapterFee
) {
count++;
}
}
address[] memory eligible = new address[](count);
uint256[] memory cycles = new uint256[](count);
uint256 j = 0;
for (uint256 i = 0; i < hearers.length && i < 1000 && j < count; i++) {
if (
hearers[i].status &&
hearers[i].ownCycle < chapterCycle &&
IERC20(chapterToken).allowance(hearers[i].hearerAddress, address(this)) >= chapterFee &&
IERC20(chapterToken).balanceOf(hearers[i].hearerAddress) >= chapterFee
) {
eligible[j] = hearers[i].hearerAddress;
cycles[j] = hearers[i].ownCycle;
j++;
}
}
return (eligible, cycles);
}
function isHearer(address hearer) external view override returns (address, string memory, uint256, bool) {
for (uint256 i = 0; i < hearers.length; i++) {
if (hearers[i].hearerAddress == hearer) {
return (
hearers[i].hearerAddress,
hearers[i].ownKey,
hearers[i].ownCycle,
hearers[i].status
);
}
}
revert("Not a hearer");
}
function getLumen(uint256 index) external view override returns (string memory, uint256, uint256, uint256) {
require(index < lumens.length, "Invalid index");
Lumen memory lumen = lumens[index];
return (lumen.dataEntry, lumen.cycle, lumen.index, lumen.timestamp);
}
function getActiveHearersCount() external view override returns (uint256) {
uint256 count = 0;
for (uint256 i = 0; i < hearers.length; i++) {
if (hearers[i].status) {
count++;
}
}
return count;
}
function nextFeeInSeconds() external view override returns (uint256, uint256, uint256) {
if (nextFee <= block.timestamp || nextFee == 0) {
return (0, 0, 0);
}
uint256 secondsLeft = nextFee - block.timestamp;
uint256 minutesLeft = secondsLeft / 60;
uint256 hoursLeft = secondsLeft / 3600;
return (secondsLeft, minutesLeft, hoursLeft);
}
function getCellHeight() external view override returns (uint256) {
return (hearers.length + 99) / 100;
}
function getCellHearerCount(uint256 cellIndex) external view override returns (uint256) {
uint256 cellHeight = (hearers.length + 99) / 100;
if (cellIndex >= cellHeight) {
return 0;
}
uint256 startIndex = cellIndex * 100;
uint256 endIndex = startIndex + 100 > hearers.length ? hearers.length : startIndex + 100;
return endIndex - startIndex;
}
function getOldOwnKey(address hearer, uint256 ownCycle) external view override returns (string memory) {
return hearerCycleToOwnKey[hearer][ownCycle];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment