Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"@openzeppelin/contracts/token/ERC20/IERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
"
},
"@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(IERC20 token, address spender, uint256 value) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
* 0 before setting it to a non-zero value.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
* Revert on invalid signature.
*/
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return
success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
}
}
"
},
"contracts/Recoverable.sol": {
"content": "// SPDX-License-Identifier: CLOSED LICENSE COPYRIGHT 2025\r
// OFFICAL DEL NORTE NETWORK COMPONENT\r
// Designed By Ken Silverman for Del Norte. Implementation help from Tony Sparks\r
// Compilation help from Maleeha Naveed. May 5th, 2025\r
\r
pragma solidity 0.8.30;\r
\r
\r
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";\r
import "../interfaces/IController.sol";\r
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";\r
\r
\r
/// @title ReversibleRecoveryBase\r
/// @notice Base contract tracking / reversing accidental ETH/ERC20 transfers with admin authorization\r
abstract contract Recoverable {\r
\r
using SafeERC20 for IERC20;\r
\r
// STRUCTS\r
\r
\r
struct ReversibleUserBalance {\r
uint256 totalReceivedThatIsReversible;\r
uint256 totalReversed;\r
uint256 totalReversals;\r
}\r
\r
\r
// -----------------------------\r
// STORAGE\r
// -----------------------------\r
\r
\r
address public controller; // Address of the Controller contract (must implement IController)\r
uint256 public ADMIN_FEE_FIXED = 10 ** 17; // 0.1 ETH\r
uint256 public totalAdminFeesCollected;\r
\r
\r
mapping(address => ReversibleUserBalance) public reversibleEthBalances;\r
mapping(address => mapping(address => ReversibleUserBalance)) public reversibleTokenBalances;\r
\r
\r
// -----------------------------\r
// EVENTS\r
// -----------------------------\r
event TransferReversed(address indexed user, uint256 refundAmount, address tokenSC, uint256 adminFee);\r
event AdminFeeUpdated(uint256 newFee);\r
event ControllerChanged(address newController);\r
\r
\r
modifier onlyBTExecutives() {\r
bool temp = IController(controller).isOfficialEntity("TreasuryAdmin", msg.sender) ||\r
IController(controller).isOfficialEntity("SmartContract", msg.sender);\r
require(temp, "Unauthorized access");\r
_; // run the code block referencing this modifier\r
}\r
\r
\r
// -----------------------------\r
// CONSTRUCTOR\r
// -----------------------------\r
constructor(address _controller) {\r
require(_controller != address(0), "Controller address cannot be zero");\r
controller = _controller;\r
}\r
\r
\r
// -----------------------------\r
// EXTERNAL METHODS\r
// -----------------------------\r
function manualUpdateReversibleBalanceETH(address userAddress, uint256 amount)\r
external onlyBTExecutives {\r
reversibleEthBalances[userAddress].totalReceivedThatIsReversible += amount;\r
}\r
\r
\r
\r
\r
function manualUpdateReversibleBalanceERC20(address userAddress, uint256 amount, address tokenSC)\r
external onlyBTExecutives {\r
reversibleTokenBalances[tokenSC][userAddress].totalReceivedThatIsReversible += amount;\r
}\r
\r
\r
function reverseAccidentalETH() external payable {\r
require(msg.value >= ADMIN_FEE_FIXED, "Insufficient admin fee");\r
require(!IController(controller).isOfficialEntity("Registrar", msg.sender),\r
"Registrars/launchpads may not be allowed to reverse any amounts they send.");\r
ReversibleUserBalance storage balance = reversibleEthBalances[msg.sender];\r
uint256 refundAmount = balance.totalReceivedThatIsReversible - balance.totalReversed;\r
require(refundAmount > 0, "Nothing to refund");\r
// Update state before external call\r
balance.totalReversed += refundAmount;\r
balance.totalReversals += 1;\r
totalAdminFeesCollected += msg.value;\r
// Perform the external call\r
(bool success, ) = msg.sender.call{value: refundAmount}("");\r
require(success, "Ether transfer failed");\r
emit TransferReversed(msg.sender, refundAmount, address(0), msg.value);\r
}\r
\r
\r
\r
function reverseAccidentalERC20(address tokenSC) external payable {\r
require(msg.value >= ADMIN_FEE_FIXED, "Insufficient admin fee");\r
require(!IController(controller).isOfficialEntity("Registrar", msg.sender), "Registrars/launchpads may not reverse any amounts they send.");\r
ReversibleUserBalance storage balance = reversibleTokenBalances[tokenSC][msg.sender];\r
uint256 refundAmount = balance.totalReceivedThatIsReversible - balance.totalReversed;\r
require(refundAmount > 0, "Nothing to refund");\r
// Update state before external call\r
balance.totalReversed += refundAmount;\r
balance.totalReversals += 1;\r
totalAdminFeesCollected += msg.value;\r
// Perform the external call\r
IERC20(tokenSC).safeTransfer(msg.sender, refundAmount);\r
emit TransferReversed(msg.sender, refundAmount, tokenSC, msg.value);\r
}\r
\r
\r
function changeAdminFee(uint256 newFee) external onlyBTExecutives {\r
ADMIN_FEE_FIXED = newFee;\r
emit AdminFeeUpdated(newFee);\r
}\r
\r
\r
function changeController(address remote) internal {\r
controller = remote;\r
emit ControllerChanged(remote);\r
}\r
}\r
"
},
"interfaces/IController.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
// Copyright 2025 US Fintech LLC and DelNorte Holdings.
//
// Permission to use, copy, modify, or distribute this software is strictly prohibited
// without prior written consent from both copyright holders.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
// ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// OFFICIAL DEL NORTE NETWORK COMPONENT
// Provides immediate membership access to platform at different levels.
// Required Non US or accredited US registration to swap for DTV token. Registration available within 180 days per terms.delnorte.io .
// Minimally tesed Conroller Tree for world-wide government administration of, well, anything, including property ownership.
// Designed by Ken Silverman as part of his ElasticTreasury (HUB and SPOKE), PeerTreasury and Controller model.
// @author Ken Silverman
// This deployment is for Del Norte Holdings, Delaware and US Fintech, LLC NY.
// Permission to change metadata stored on blockchain explorers and elsewhere granted to:
// Del Norte Holdings, DE only and/or US Fintech, LLC NY independently
pragma solidity 0.8.30;
interface IController {
struct OfficialEntityStruct {
string fullNameOfEntityOrLabel;
string nationalIdOfEntity;
address pubAddress;
uint256 blockNumber;
uint256 blockTimestamp;
bool active;
uint256 value; // Associated value (0-1,000,000 for absolute, or basis points for %, type(uint256).max = no value)
}
struct ChainedEntityStruct {
address entityAddress;
address parent;
uint8 type1; // 0 = 'V' (value/absolute), 1 = 'P' (percentage in basis points)
uint256 val1; // Value for type1 (absolute amount or percentage in basis points)
uint8 type2; // 0 = 'V' (value/absolute), 1 = 'P' (percentage in basis points)
uint256 val2; // Value for type2 (absolute amount or percentage in basis points)
bool active;
uint256 blockNumber;
uint256 blockTimestamp;
string entityName; // Human-readable name
string entityID; // Additional identifier (tax ID, registration #, etc)
}
struct CalculatedEntityAmount {
address entityAddress;
uint256 type1Amount; // Calculated amount for type1 (e.g., transfer fee)
uint256 type2Amount; // Calculated amount for type2 (e.g., activation fee)
}
// Official Entity Functions
function addOfficialEntity(string memory, address, string memory, string memory) external returns (bool);
function addOfficialEntityWithValue(string memory, address, string memory, string memory, uint256) external returns (bool);
function removeOfficialEntity(string memory, address) external returns (bool);
function isOfficialEntity(string memory, address) external view returns (bool);
function isOfficialEntityFast(bytes32, address) external view returns (bool);
function isOfficialDoubleEntityFast(bytes32, address, bytes32, address, bool) external view returns (bool);
function isOfficialTripleEntityFast(bytes32, address, bytes32, address, bytes32, address, bool) external view returns (bool);
function isOfficialQuadrupleEntityFast(bytes32, address, bytes32, address, bytes32, address, bytes32, address, bool) external view returns (bool);
function getOfficialEntity(string calldata, address) external view returns (OfficialEntityStruct memory);
function getAllOfficialEntities(string calldata, bool) external view returns (OfficialEntityStruct[] memory);
function init(address, string calldata) external;
// Chained Entity Functions
function addChainedEntity(string memory, address, address, uint8, uint256, uint8, uint256, string memory, string memory) external returns (bool);
function removeChainedEntity(string calldata, address, bool) external returns (bool);
function isChainedEntity(string calldata, address) external view returns (bool);
function isChainedEntityFast(bytes32, address) external view returns (bool);
function getChainedEntity(string calldata, address) external view returns (ChainedEntityStruct memory);
function getActiveChainedEntities(string calldata) external view returns (ChainedEntityStruct[] memory);
function calculateChainedAmounts(string calldata, uint256, bool, uint256, uint8) external view returns (CalculatedEntityAmount[] memory, uint256, uint256);
// Constants
function BASIS_POINTS_DIVISOR() external view returns (uint256);
}
"
},
"contracts/Controller.sol": {
"content": "// SPDX-License-Identifier: UNLICENSED
// Copyright 2025 US Fintech LLC and DelNorte Holdings.
//
// Permission to use, copy, modify, or distribute this software is strictly prohibited
// without prior written consent from either copyright holders.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
// CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
// ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// OFFICIAL DEL NORTE NETWORK COMPONENT
// This component is minimally tested. Use at your own risk.
// Designed by Ken Silverman as part of his ExecutivePeerTreasury, Controller and IssueToken model.
// Complete Asset Management System for all assets, including real estate, stocks, bonds, etc.
// This deployment is for the Data Cloud OS Desktop enterprise CRM and management system owned by US Fintech, LLC.
// Earlier Deployments for Trueviewchain Inc. a Panama entity and Del Norte El Salvador S.A a subsidiary of Del Norte Holdings, Delaware USA.
// Permission to change metadata stored on blockchain explorers and elsewhere granted to US Fintech, LLC NY Del Norte Holdings, DE only.
// Early Compilation help from Tony Sparks and Maleeha Naveed.
// Latest Version: Deployed by Maleeha Naveed on behalf of US Fintech. October 14th, 2025
pragma solidity 0.8.30;
import "./Recoverable.sol";
interface IERC20SymbolAndName {
function symbol() external view returns (string memory);
function name() external view returns (string memory);
}
/// @title Controller
/// @author Ken Silverman
/// @notice This contract is used to manage the official entities of all smart contract in the network.
contract Controller is Recoverable {
event TreasuryCreated(address treasuryAddress, string label);
event UpdateOfficials(
address indexed entityAddress,
address indexed updater,
string entityType, // "SC,SystemUser,SystemAdmin,BTADmin, etc ..."
string action, // Add,Remove (nothing is actually removed, only active boolean changed)
string fullNameOfEntityOrLabel, // person's full name or "SC"
string nationalIdOfEntity, // registered to entity or SC address if entity is SC
uint256 indexed blockTimeStamp, // block number of the event
bool remainsActive
);
event UpdateOfficialsWithValue(
address indexed entityAddress,
address indexed updater,
string entityType,
string action,
string fullNameOfEntityOrLabel,
string nationalIdOfEntity,
uint256 indexed blockTimeStamp,
bool remainsActive,
uint256 value // The associated value
);
event ChainedEntityUpdate(
address indexed entityAddress,
address indexed parent,
address indexed updater,
string chainType,
string action, // "Add", "Remove", "Reactivate"
uint8 type1, // 0 = 'V', 1 = 'P'
uint256 val1,
uint8 type2, // 0 = 'V', 1 = 'P'
uint256 val2,
bool active,
uint256 blockTimeStamp
);
// Constant for chained entity calculations (basis points)
uint256 public constant BASIS_POINTS_DIVISOR = 10000; // 100% = 10000 basis points
struct OfficialEntityStruct {
string fullNameOfEntityOrLabel;
string nationalIdOfEntity; // (if entity is a smart contract, leave empty or provide some other detail here.)
address pubAddress; // registered to entity or SC address if entity is SC
uint256 blockNumber;
uint256 blockTimestamp;
bool active;
uint256 value; // Associated value (0-1,000,000 for absolute, or basis points for %, type(uint256).max = no value)
}
struct OfficialEntityGroup {
mapping(address => OfficialEntityStruct) entitiesByAddress; // Maps entity address to its details
address[] entityList; // List of entity addresses for iteration
}
struct AdminTypeRegistry {
mapping(string => bool) isRegistered; // internally a hash key but requires string for lookup
mapping(bytes32 => bool) isHashRegistered; // slightly faster pre-hahsed lookup
string[] registeredAdminTypes;
mapping(bytes32 => string) nameByHash; // Reverse lookup from hash to original string
}
// ChainedEntity structures for hierarchical fee/permission management
struct ChainedEntityStruct {
address entityAddress;
address parent; // address(0) for root nodes
uint8 type1; // 0 = 'V' (value/absolute), 1 = 'P' (percentage in basis points)
uint256 val1; // Value for type1 (absolute amount or percentage in basis points)
uint8 type2; // 0 = 'V' (value/absolute), 1 = 'P' (percentage in basis points)
uint256 val2; // Value for type2 (absolute amount or percentage in basis points)
bool active;
uint256 blockNumber;
uint256 blockTimestamp;
string entityName; // Human-readable name
string entityID; // Additional identifier (tax ID, registration #, etc)
}
struct CalculatedEntityAmount {
address entityAddress;
uint256 type1Amount; // Calculated amount for type1 (e.g., transfer fee)
uint256 type2Amount; // Calculated amount for type2 (e.g., activation fee)
}
struct ChainedEntityGroup {
mapping(address => ChainedEntityStruct) entitiesByAddress;
mapping(address => address[]) childrenByParent; // Track children for recursive operations
address[] rootNodes; // Top nodes in each tree of the forest
}
mapping(string => OfficialEntityGroup) private officialEntityGroupsByType;
mapping(string => ChainedEntityGroup) private chainedEntityGroupsByType;
// NEW structure to SUPPORT validateAdminType
AdminTypeRegistry private adminTypeRegistry;
// address public remoteController; // Hub address for admin verification - ALWAYS "this" for now
string public platformName;
string public projectName; // Project name (fixed at deployment)
string public projectId; // Project ID (fixed at deployment)
bytes32 public constant KECCAK_TREASURY_ADMIN = keccak256(bytes("TreasuryAdmin"));
bytes32 public constant KECCAK_SMART_CONTRACT = keccak256(bytes("SmartContract"));
bytes32 public constant KECCAK_SUPER_ADMIN = keccak256(bytes("Super"));
uint256 public constant NO_VALUE = type(uint256).max; // Sentinel value indicating "no value set"
uint256 public constant MAX_VALUE = 1000000; // Maximum allowed value
modifier onlySuper() {
require(isOfficialEntityFast(KECCAK_SUPER_ADMIN, tx.origin), "caller not Super");
_;
}
// Warning! Any official "Super" can call addEntity directly. This is NOT a first line of defense against that.
// The only defense there is to make sure your official SCs have their own calls to this controller’s
// isOfficialEntity(“Super”) for example BEFORE calling addEntity() or removeEntity()
// init() LOCKS this Controller to only allowing TreasAdmins to adding official SCs to begin with.
// REMOTE case now allows a smartContract caller unless tx.origin is used
// use case of checking that the original human sender is authorized (even through non-official contract calls)
// may be one of the few legitimate uses of tx.origin
// ✓ To register as official smartContract → You need to be called by an official entity
// ✗ because you can't be an official entity smartContract → Until that smatcontract calls init() via its own constructor
// therefore we must USE tx.origin for initial registration of smartContracts as a bootstrap
modifier onlySuperOrSCExecutives() {
bool temp = isOfficialEntityFast(KECCAK_SUPER_ADMIN, tx.origin) ||
isOfficialEntityFast(KECCAK_SMART_CONTRACT, msg.sender);
require(temp, "Unauthorized access");
_; // run the code block referencing this modifier
}
// to meet the 24K limit, BaseTreasury is now instantiated FIRST - however its concept remains the same - as a BASE class
// for ElasticTreasuryHUB and ElasticTreasurySPOKE MasterHub is now an optional, later passed "controller" that must be
// a separate instantiatedController. This will NOT be used for our needs yet. In other words, controller is ALWAYS
// this for now. initialEntitys are project super entity. NOT the same as the platform chained entity.
constructor(
address projectSuperAddress,
string memory _platformName,
address platformTreasuryAddress,
string memory projectSuperName,
string memory projectSuperId,
string memory _projectName,
string memory _projectId
) Recoverable(address(this)) {
platformName = _platformName;
projectName = _projectName; // Store immutable
projectId = _projectId; // Store immutable
// remoteController = address(this); // formerly masterHub, controller is ALWAYS this for now. (see remotelyManage)
// **Reject if projectSuperAddress is 0x0000**
if (projectSuperAddress == address(0)) {
revert("Controller: needs at least one Super Entity");
}
// Register default admin types
string[3] memory defaultTypes = ["TreasuryAdmin", "SmartContract","Super"];
for (uint256 i = 0; i < defaultTypes.length; i++) {
addAdminType(defaultTypes[i]);
}
// Use passed entity name/ID or defaults if empty
string memory superName = bytes(projectSuperName).length > 0 ? projectSuperName : "Presumed Multi-Sig project officer";
string memory superID = bytes(projectSuperId).length > 0 ? projectSuperId : "Initial Execs";
addOfficialEntityNow("Super", projectSuperAddress, superName, superID, NO_VALUE);
addOfficialEntityNow("TreasuryAdmin", projectSuperAddress, superName, superID, NO_VALUE);
// Add platform treasury as root chained entity if provided
if (platformTreasuryAddress != address(0)) {
addChainedEntity("PLATFORM_CHAIN_TREASURY", address(0), platformTreasuryAddress, 0, 20, 0, 1000, platformName, "Platform Treasury");
}
emit TreasuryCreated(address(this), "Controller");
}
// caller MUST be a HUB or SPOKE because Controller is now deployed separately.
// HUB and spoke are no longer Controllers themselves. addedBTAdmin is optional, HUB and SPOKE pass address(0)
function init(address officialSmartContractAddress, string calldata contractName) external onlySuperOrSCExecutives {
addOfficialEntityNow("SmartContract",officialSmartContractAddress, contractName,"", NO_VALUE); // may exist, if token, symbol will be added.
}
// do not use except for use case as of yet not imagined would need to be called immediately via child constructor
/*
function remotelyManage(address _controller) external onlySuperOrSCExecutives {
bool isRemotelyManaged = (_controller != address(0) && _controller != address(this));
if (isRemotelyManaged) {
remoteController = _controller;
}
else {
remoteController = address(this); // not used if isRemotelyManaged is false
}
changeController(remoteController);
} */
// if master is self, it is the same as passing false, because SELF (local) WILL manage in that case.
// Any controller that is not self must be another instance of BaseTreasury
// just a formality, if not here, controller can always be cast to BaseTreasury, even if self.
// Ex: BaseTreasury(controller).someFunction … will always work whether self or another BaseTreasury instance.
//function doesRemoteControllerManageAllOfficials() internal view returns (bool) {
// return controller != address(this);
//}
///
// OFFICIAL ENTITIES
//
//
// Dynamically validate an admin type, and optionally register new ones
function validateAdminType(string memory adminType) internal view {
if (!adminTypeRegistry.isRegistered[adminType]) {
revert("Invalid admin type");
}
}
function addAdminType(string memory adminType) internal returns (bool) {
if (!adminTypeRegistry.isRegistered[adminType]) {
// to support remote case, let a TreasuryAdmin add too, since “SmartContract” is not sensed for remote
// chained transaction caller: EOA ⇒ contract A == > contract B (here) ⇒ remote controller (A not sensed)
// require(isOfficialEntity(“SmartContract”,msg.sender),”Only official SC can add new Officl type”);
bytes32 hash = keccak256(bytes(adminType));
adminTypeRegistry.isRegistered[adminType] = true;
adminTypeRegistry.isHashRegistered[hash] = true;
adminTypeRegistry.nameByHash[hash] = adminType;
adminTypeRegistry.registeredAdminTypes.push(adminType);
return true;
}
return false;
}
// Retrieve all registered admin types
function getRegisteredAdminTypes() external view returns (string[] memory) {
return adminTypeRegistry.registeredAdminTypes;
}
// "TreasuryAdmin", initialControllerAdmin, "Presumed Multi-Sig platform exec", "Initial Execs"
function addOfficialEntity(string memory adminType, address entityAddr, string memory label,string memory nationalIdOrSC) public onlySuper returns (bool) {
return addOfficialEntityNow(adminType, entityAddr, label, nationalIdOrSC, NO_VALUE);
}
// Add official entity with an associated value
function addOfficialEntityWithValue(string memory adminType, address entityAddr, string memory label, string memory nationalIdOrSC, uint256 value) public onlySuper returns (bool) {
require(value <= MAX_VALUE, "Value exceeds maximum");
return addOfficialEntityNow(adminType, entityAddr, label, nationalIdOrSC, value);
}
function addOfficialEntityNow(string memory adminType, address entityAddr, string memory label, string memory nationalIdOrSCSymbol, uint256 value) internal returns (bool) {
//if (doesRemoteControllerManageAllOfficials()) {
// // For remote controller, route based on whether value is set
// if (value == NO_VALUE) {
// return Controller(controller).addOfficialEntity(adminType, entityAddr, label, nationalIdOrSCSymbol);
// } else {
// return Controller(controller).addOfficialEntityWithValue(adminType, entityAddr, label, nationalIdOrSCSymbol, value);
// }
//}
require(entityAddr != address(0), "Invalid entity address");
if (bytes(label).length == 0) {
if (entityAddr.code.length > 0) {
try IERC20SymbolAndName(entityAddr).name() returns (string memory s) {
label = bytes(s).length > 0 ? s : "SC not token";
} catch {
label = "SC not token";
}
}
else {
label = "SC not token";
}
}
if (bytes(nationalIdOrSCSymbol).length == 0) {
if (entityAddr.code.length > 0) {
try IERC20SymbolAndName(entityAddr).symbol() returns (string memory s) {
nationalIdOrSCSymbol = bytes(s).length > 0 ? s : "SC not token";
} catch {
nationalIdOrSCSymbol = "SC not token";
}
} else {
nationalIdOrSCSymbol = "no label and SC not contract";
}
}
addAdminType(adminType);
OfficialEntityGroup storage group = officialEntityGroupsByType[adminType];
// If entity is active, check if we're trying to update the value
if (group.entitiesByAddress[entityAddr].active) {
uint256 existingValue = group.entitiesByAddress[entityAddr].value;
// Prevent changing from value to NO_VALUE or vice versa
// This is a semantic change that requires removal and re-addition
if ((existingValue == NO_VALUE && value != NO_VALUE) ||
(existingValue != NO_VALUE && value == NO_VALUE)) {
revert("Cannot change from value to noVal or vice versa. Must remove entity first");
}
// Allow updating to a different value (both must be real values, not NO_VALUE)
if (value != NO_VALUE && existingValue != value) {
// Value is changing, allow update
group.entitiesByAddress[entityAddr].value = value;
emit UpdateOfficialsWithValue(entityAddr, msg.sender, adminType, "UpdateValue", label, nationalIdOrSCSymbol, block.timestamp, true, value);
return true;
}
return false; // Already exists with same value or no value change
}
group.entitiesByAddress[entityAddr] = OfficialEntityStruct(label, nationalIdOrSCSymbol, entityAddr, block.number, block.timestamp, true, value);
group.entityList.push(entityAddr);
// Emit appropriate event based on whether value is set
if (value == NO_VALUE) {
emit UpdateOfficials(entityAddr, msg.sender, adminType, "Add", label, nationalIdOrSCSymbol, block.timestamp, true);
} else {
emit UpdateOfficialsWithValue(entityAddr, msg.sender, adminType, "Add", label, nationalIdOrSCSymbol, block.timestamp, true, value);
}
return true;
}
function removeOfficialEntity(string calldata adminType, address entityAddr) external onlySuper returns (bool) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).removeOfficialEntity(adminType, entityAddr);
//}
require(entityAddr != address(0), "Invalid entity address");
validateAdminType(adminType); // ???? Ensure valid type
OfficialEntityGroup storage group = officialEntityGroupsByType[adminType];
if (!group.entitiesByAddress[entityAddr].active) {
return false;
}
// Soft-delete by marking inactive, but retain in list for history
group.entitiesByAddress[entityAddr].active = false;
emit UpdateOfficials(entityAddr, msg.sender, adminType, "Remove",group.entitiesByAddress[entityAddr].fullNameOfEntityOrLabel,group.entitiesByAddress[entityAddr].nationalIdOfEntity,block.timestamp,false);
return true;
}
function isOfficialEntityFast(bytes32 hashedAdminType, address entityAddr) public view returns (bool) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).isOfficialEntityFast(hashedAdminType, entityAddr);
//}
require(entityAddr != address(0), "Invalid entity address");
string memory adminType = adminTypeRegistry.nameByHash[hashedAdminType];
if (!adminTypeRegistry.isHashRegistered[hashedAdminType]) {
revert("Invalid admin type");
}
return officialEntityGroupsByType[adminType].entitiesByAddress[entityAddr].active;
}
function isOfficialEntity(string memory adminType, address entityAddr) public view returns (bool) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).isOfficialEntity(adminType, entityAddr);
//}
require(entityAddr != address(0), "Invalid entity address");
validateAdminType(adminType); // ???? Ensure valid type
return officialEntityGroupsByType[adminType].entitiesByAddress[entityAddr].active;
}
function getOfficialEntity(string calldata adminType, address entityAddr) external view returns (OfficialEntityStruct memory) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).getOfficialEntity(adminType, entityAddr);
//}
require(entityAddr != address(0), "Invalid entity addrss");
validateAdminType(adminType); // ???? Ensure valid type
OfficialEntityStruct storage entity = officialEntityGroupsByType[adminType].entitiesByAddress[entityAddr];
require(entity.active, "No Ent or inactve");
return entity;
}
function getAllOfficialEntities(string calldata adminType, bool includeInactive) external view returns (OfficialEntityStruct[] memory) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).getAllOfficialEntities(adminType, includeInactive);
//}
validateAdminType(adminType); // ???? Ensure valid type
OfficialEntityGroup storage group = officialEntityGroupsByType[adminType];
// First count entities based on the includeInactive parameter
uint256 entityCount = 0;
for (uint256 i = 0; i < group.entityList.length; i++) {
address entityAddr = group.entityList[i];
if (includeInactive || group.entitiesByAddress[entityAddr].active) {
entityCount++;
}
}
// Then create array of exactly the right size and fill it
OfficialEntityStruct[] memory filteredEntities = new OfficialEntityStruct[](entityCount);
uint256 counter = 0;
for (uint256 i = 0; i < group.entityList.length; i++) {
address entityAddr = group.entityList[i];
if (includeInactive || group.entitiesByAddress[entityAddr].active) {
filteredEntities[counter] = group.entitiesByAddress[entityAddr];
counter++;
}
}
return filteredEntities;
}
function isOfficialDoubleEntityFast(bytes32 adminType1, address entityAddr1,bytes32 adminType2, address entityAddr2, bool isAnd) external view returns (bool) {
if (isAnd) {
return isOfficialEntityFast(adminType1, entityAddr1) && isOfficialEntityFast(adminType2, entityAddr2);
} else {
return isOfficialEntityFast(adminType1, entityAddr1) || isOfficialEntityFast(adminType2, entityAddr2);
}
}
// Fast triple entity check with bytes32
function isOfficialTripleEntityFast(bytes32 adminType1, address entityAddr1, bytes32 adminType2, address entityAddr2,
bytes32 adminType3, address entityAddr3, bool isAnd) external view returns (bool) {
if (isAnd) {
return isOfficialEntityFast(adminType1, entityAddr1)
&& isOfficialEntityFast(adminType2, entityAddr2)
&& isOfficialEntityFast(adminType3, entityAddr3);
} else {
return isOfficialEntityFast(adminType1, entityAddr1)
|| isOfficialEntityFast(adminType2, entityAddr2)
|| isOfficialEntityFast(adminType3, entityAddr3);
}
}
function isOfficialQuadrupleEntityFast(bytes32 adminType1, address entityAddr1,bytes32 adminType2, address entityAddr2,bytes32 adminType3, address entityAddr3,
bytes32 adminType4, address entityAddr4,bool isAnd) external view returns (bool) {
if (isAnd) {
return isOfficialEntityFast(adminType1, entityAddr1)
&& isOfficialEntityFast(adminType2, entityAddr2)
&& isOfficialEntityFast(adminType3, entityAddr3)
&& isOfficialEntityFast(adminType4, entityAddr4);
} else {
return isOfficialEntityFast(adminType1, entityAddr1)
|| isOfficialEntityFast(adminType2, entityAddr2)
|| isOfficialEntityFast(adminType3, entityAddr3)
|| isOfficialEntityFast(adminType4, entityAddr4);
}
}
//
// END OFFICIAL ENTITIES
//
//
// CHAINED ENTITIES - Hierarchical tree/forest structure for fee management
//
/// @notice Add a chained entity to the forest. Only SUPER can add NEW root nodes.
/// Root nodes can update their own value or re-add themselves if previously removed.
/// Entities already in the forest can add children under themselves.
/// @param chainType The type of chain (e.g., "FeeAdmin")
/// @param parent The parent address (address(0) for root nodes)
/// @param newEntity The address to add
/// @param type1 Type of value 1: 0 = 'V' (absolute), 1 = 'P' (percentage in basis points)
/// @param val1 Value 1 amount (absolute or percentage depending on type1)
/// @param type2 Type of value 2: 0 = 'V' (absolute), 1 = 'P' (percentage in basis points)
/// @param val2 Value 2 amount (absolute or percentage depending on type2)
/// @param entityName Human-readable name of the entity
/// @param entityID Additional identifier (tax ID, registration #, etc)
function addChainedEntity(string memory chainType, address parent, address newEntity, uint8 type1, uint256 val1, uint8 type2, uint256 val2, string memory entityName, string memory entityID) public returns (bool) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).addChainedEntity(chainType, parent, newEntity, type1, val1, type2, val2, entityName, entityID);
//}
require(newEntity != address(0), "Invalid entity address");
require(type1 <= 1, "Invalid type1 (must be 0 or 1)");
require(type2 <= 1, "Invalid type2 (must be 0 or 1)");
require(val1 <= MAX_VALUE, "Val1 exceeds maximum");
require(val2 <= MAX_VALUE, "Val2 exceeds maximum");
addAdminType(chainType);
ChainedEntityGroup storage group = chainedEntityGroupsByType[chainType];
// Determine if caller has permission
bool isRootNode = (parent == address(0));
bool isSuperOrSC = isOfficialEntityFast(KECCAK_SUPER_ADMIN, tx.origin) ||
isOfficialEntityFast(KECCAK_SMART_CONTRACT, msg.sender);
bool isCallerInChain = group.entitiesByAddress[msg.sender].active;
// Check if entity already exists
if (group.entitiesByAddress[newEntity].entityAddress != address(0)) {
ChainedEntityStruct storage existing = group.entitiesByAddress[newEntity];
bool isExistingRoot = (existing.parent == address(0));
// Special case: Root node updating own value or reactivating self
if (isExistingRoot && newEntity == msg.sender && parent == address(0)) {
// Root node can always update own value or reactivate self
if (existing.active) {
// Update value while active
existing.type1 = type1;
existing.val1 = val1;
existing.type2 = type2;
existing.val2 = val2;
existing.entityName = entityName;
existing.entityID = entityID;
existing.blockTimestamp = block.timestamp;
emit ChainedEntityUpdate(newEntity, parent, msg.sender, chainType, "UpdateValue", type1, val1, type2, val2, true, block.timestamp);
return true;
} else {
// Reactivate self as root
existing.active = true;
existing.type1 = type1;
existing.val1 = val1;
existing.type2 = type2;
existing.val2 = val2;
existing.entityName = entityName;
existing.entityID = entityID;
existing.blockTimestamp = block.timestamp;
emit ChainedEntityUpdate(newEntity, parent, msg.sender, chainType, "ReactivateSelf", type1, val1, type2, val2, true, block.timestamp);
return true;
}
}
// Regular reactivation: same parent, entity inactive
if (existing.parent == parent && !existing.active) {
// If non-root trying to re-add with same parent (requires parent or SUPER)
require(msg.sender == existing.parent || isSuperOrSC, "Only parent or SUPER can reactivate");
existing.active = true;
existing.type1 = type1;
existing.val1 = val1;
existing.type2 = type2;
existing.val2 = val2;
existing.entityName = entityName;
existing.entityID = entityID;
existing.blockTimestamp = block.timestamp;
emit ChainedEntityUpdate(newEntity, parent, msg.sender, chainType, "Reactivate", type1, val1, type2, val2, true, block.timestamp);
return true;
}
// Already exists and active, or trying to change parent
return false;
}
// Adding NEW entity - check permissions
if (isRootNode) {
// Only SUPER can add NEW root nodes (not self-updates, those handled above)
require(isOfficialEntityFast(KECCAK_SUPER_ADMIN, tx.origin), "Only SUPER can add new root nodes");
} else {
// Must be: SUPER, official SmartContract, OR already an active entity in the chain
require(isSuperOrSC || isCallerInChain, "Must be SUPER, SC executive, or active in chain");
// If caller is in chain, they can only add children under themselves
if (!isSuperOrSC && isCallerInChain) {
require(parent == msg.sender, "Non-SUPER entities can only add children under themselves");
}
// Parent must exist and be active in the chain
require(group.entitiesByAddress[parent].active, "Parent not in active chain");
}
// Adding new entity
if (isRootNode) {
group.rootNodes.push(newEntity);
} else {
// Add to parent's children list
group.childrenByParent[parent].push(newEntity);
}
// Create the new entity
group.entitiesByAddress[newEntity] = ChainedEntityStruct({
entityAddress: newEntity,
parent: parent,
type1: type1,
val1: val1,
type2: type2,
val2: val2,
active: true,
blockNumber: block.number,
blockTimestamp: block.timestamp,
entityName: entityName,
entityID: entityID
});
emit ChainedEntityUpdate(newEntity, parent, msg.sender, chainType, "Add", type1, val1, type2, val2, true, block.timestamp);
return true;
}
/// @notice Remove a chained entity. Only parent can remove children, only SELF can remove root nodes.
/// @dev Recursively deactivates descendants. If includeSelf=false, only children are removed.
/// @param chainType The chain type (e.g., "FeeAdmin")
/// @param entity The entity to process
/// @param includeSelf If true, removes entity and descendants. If false, only removes descendants.
function removeChainedEntity(string calldata chainType, address entity, bool includeSelf) external returns (bool) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).removeChainedEntity(chainType, entity, includeSelf);
//}
require(entity != address(0), "Invalid entity address");
validateAdminType(chainType);
ChainedEntityGroup storage group = chainedEntityGroupsByType[chainType];
ChainedEntityStruct storage entityStruct = group.entitiesByAddress[entity];
require(entityStruct.active, "Entity not active");
// Check permissions - must be SELF or parent
bool isRootNode = (entityStruct.parent == address(0));
if (isRootNode) {
// Only SELF can remove root node or its children
require(msg.sender == entity, "Only SELF can remove root node");
} else {
// Only parent can remove child (or entity itself can remove self/children)
require(msg.sender == entityStruct.parent || msg.sender == entity, "Only parent or SELF can remove");
}
// Deactivate entity and/or descendants
_deactivateChainedEntity(chainType, entity, includeSelf);
return true;
}
/// @notice Internal function to recursively deactivate entity and/or descendants
/// @param chainType The chain type
/// @param entity The entity to process
/// @param deactivateSelf If true, deactivates this entity. Always deactivates all descendants.
function _deactivateChainedEntity(string memory chainType, address entity, bool deactivateSelf) internal {
ChainedEntityGroup storage group = chainedEntityGroupsByType[chainType];
ChainedEntityStruct storage entityStruct = group.entitiesByAddress[entity];
if (!entityStruct.active) {
return; // Already inactive
}
// Deactivate this entity if requested
if (deactivateSelf) {
entityStruct.active = false;
emit ChainedEntityUpdate(entity, entityStruct.parent, msg.sender, chainType, "Remove", entityStruct.type1, entityStruct.val1, entityStruct.type2, entityStruct.val2, false, block.timestamp);
}
// Recursively deactivate all children (always)
address[] storage children = group.childrenByParent[entity];
for (uint256 i = 0; i < children.length; i++) {
_deactivateChainedEntity(chainType, children[i], true); // Always deactivate descendants
}
}
/// @notice Check if an address is an active chained entity
function isChainedEntity(string calldata chainType, address entity) external view returns (bool) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).isChainedEntity(chainType, entity);
//}
validateAdminType(chainType);
return chainedEntityGroupsByType[chainType].entitiesByAddress[entity].active;
}
/// @notice Check if an address is an active chained entity (fast version with hash)
function isChainedEntityFast(bytes32 hashedChainType, address entity) external view returns (bool) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).isChainedEntityFast(hashedChainType, entity);
//}
string memory chainType = adminTypeRegistry.nameByHash[hashedChainType];
require(adminTypeRegistry.isHashRegistered[hashedChainType], "Invalid chain type");
return chainedEntityGroupsByType[chainType].entitiesByAddress[entity].active;
}
/// @notice Get chained entity details including parent and value
function getChainedEntity(string calldata chainType, address entity) external view returns (ChainedEntityStruct memory) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).getChainedEntity(chainType, entity);
//}
validateAdminType(chainType);
ChainedEntityStruct storage entityStruct = chainedEntityGroupsByType[chainType].entitiesByAddress[entity];
require(entityStruct.active, "Entity not active");
return entityStruct;
}
/// @notice Get all active chained entities in a forest (for fee distribution)
/// @return Array of ChainedEntityStruct containing all active entities
function getActiveChainedEntities(string calldata chainType) external view returns (ChainedEntityStruct[] memory) {
//if (doesRemoteControllerManageAllOfficials()) {
// return Controller(controller).getActiveChainedEntities(chainType);
//}
validateAdminType(chainType);
ChainedEntityGroup storage group = chainedEntityGroupsByType[chainType];
// First, count active entities by traversing from root nodes
uint256 count = 0;
for (uint256 i = 0; i < group.rootNodes.length; i++) {
count += _countActiveDescendants(chainType, group.rootNodes[i]);
}
// Allocate array
ChainedEntityStruct[] memory entities = new ChainedEntityStruct[](count);
// Fill array
uint256 index = 0;
for (uint256 i = 0; i < group.rootNodes.length; i++) {
index = _collectActiveDescendants(chainType, group.rootNodes[i], entities, index);
}
return entities;
}
/// @notice Internal function to count active descendants recursively
function _countActiveDescendants(string memory chainType, address entity) internal view returns (uint256) {
ChainedEntityGroup storage group = chainedEntityGroupsByType[chainType];
ChainedEntityStruct storage entityStruct = group.entitiesByAddress[entity];
if (!entityStruct.active) {
return 0;
}
uint256 count = 1; // Count self
// Count children
address[] storage children = group.childrenByParent[entity];
for (uint256 i = 0; i < children.length; i++) {
count += _countActiveDescendants(chainType, children[i]);
}
return count;
}
/// @notice Internal function to collect active descendants recursively
function _collectActiveDescendants(string memory chainType, address entity, ChainedEntityStruct[] memory entities, uint256 index) internal view returns (uint256) {
ChainedEntityGroup storage group = chainedEntityGroupsByType[chainType];
ChainedEntityStruct storage entityStruct = group.entitiesByAddress[entity];
if (!entityStruct.active) {
return index;
}
// Add self (copy to memory)
entities[index] = ChainedEntityStruct({
entityAddress: entityStruct.entityAddress,
parent: entityStruct.parent,
type1: entityStruct.type1,
val1: entityStruct.val1,
type2: entityStruct.type2,
val2: entityStruct.val2,
active: entityStruct.active,
blockNumber: entityStruct.blockNumber,
blockTimestamp: entityStruct.blockTimestamp,
entityName: entityStruct.entityName,
entityID: entityStruct.entityID
});
index++;
// Add children
address[] storage children = group.childrenByParent[entity];
for (uint256 i = 0; i < children.length; i++) {
index = _collectActiveDescendants(chainType, children[i], entities, index);
}
return index;
}
/**
* @notice Calculate chained amounts for hierarchical entities (fees, royalties, etc.)
* @param chainType The chain type (e.g., "PLATFORM_CHAIN_TREASURER")
* @param totalVal Base value (0 = NO_VALUE, meaning only absolute values apply)
* @param isPctRelative If true, percentages are multiplicative (20% of 10% = 2%). If false, additive (20% + 10% = 30%)
* @param maxPct Maximum cumulative percentage allowed in basis points (typically 10000 = 100%)
* @param calculateType Which type to calculate: 0 = type1 only, 1 = type2 only, 2 = both (gas optimization)
* @return calculatedAmounts Array of calculated amounts per entity (address + type1Amount + type2Amount)
* @return totalType1 Sum of all type1 amounts
* @return totalType2 Sum of all type2 amounts
*/
function calculateChainedAmounts(
string calldata chainType,
uint256 totalVal,
bool isPctRelative,
uint256 maxPct,
uint8 calculateType
) external view returns (
CalculatedEntityAmount[] memory calculatedAmounts,
uint256 totalType1,
uint256 totalType2
) {
// Get active entities (ordered parent-first by depth-first traversal)
ChainedEntityStruct[] memory chainedEntities = this.getActiveChainedEntities(chainType);
uint256[] memory type1Amounts = new uint256[](chainedEntities.length);
uint256[] memory type2Amounts = new uint256[](chainedEntities.length);
// Track parent values for percentage calculations (memory arrays)
uint256[] memory type1ParentVals = new uint256[](chainedEntities.length);
uint256[] memory type2ParentVals = new uint256[](chainedEntities.length);
uint256 cumulativePct1 = 0;
uint256 cumulativePct2 = 0;
totalType1 = 0;
totalType2 = 0;
// Process each entity
for (uint256 i = 0; i < chainedEntities.length; i++) {
ChainedEntityStruct memory entity = chainedEntities[i];
// === CALCULATE TYPE1 (if calculateType == 0 or 2) ===
if (calculateType == 0 || calculateType == 2) {
uint256 type1Amt = 0;
if (entity.type1 == 0) {
// Type 'V' = absolute value
type1Amt = entity.val1;
} else if (entity.type1 == 1) {
// Type 'P' = percentage
if (entity.parent == address(0)) {
// Root node: percentage of totalVal
if (totalVal > 0) {
type1Amt = (totalVal * entity.val1) / BASIS_POINTS_DIVISOR;
}
// If totalVal == 0, type1Amt = 0
} else {
// Child n
Submitted on: 2025-11-03 18:50:24
Comments
Log in to comment.
No comments yet.