Skip to content

Instantly share code, notes, and snippets.

@AnirudhMemani
Created March 9, 2025 07:56
Show Gist options
  • Save AnirudhMemani/c4de4f634c362c5720dd22eef1534163 to your computer and use it in GitHub Desktop.
Save AnirudhMemani/c4de4f634c362c5720dd22eef1534163 to your computer and use it in GitHub Desktop.
Mini technical task
# Income Distribution Solutions for Security Tokens
## Solution 1: Push-Based Distribution with Batching
The first approach uses a traditional "push" model where the contract distributes income to all token holders. To make this gas-efficient, we'll implement batching.
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract RealEstateToken is ERC20, Ownable {
mapping(address => uint256) public lastClaimedDistribution;
uint256 public currentDistributionId = 0;
mapping(uint256 => uint256) public distributionAmountPerToken;
mapping(uint256 => uint256) public distributionTimestamp;
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function deposit() external payable onlyOwner {
// Income received from real estate
currentDistributionId++;
uint256 totalSupply = totalSupply();
distributionAmountPerToken[currentDistributionId] = msg.value * 1e18 / totalSupply; // Scale for precision
distributionTimestamp[currentDistributionId] = block.timestamp;
emit IncomeDeposited(currentDistributionId, msg.value);
}
function distributeIncome(address[] calldata recipients, uint256 batchSize) external {
require(batchSize > 0 && batchSize <= recipients.length, "Invalid batch size");
for (uint256 i = 0; i < batchSize; i++) {
address recipient = recipients[i];
uint256 lastClaimed = lastClaimedDistribution[recipient];
uint256 balance = balanceOf(recipient);
uint256 amount = 0;
for (uint256 distId = lastClaimed + 1; distId <= currentDistributionId; distId++) {
amount += balance * distributionAmountPerToken[distId] / 1e18;
}
if (amount > 0) {
lastClaimedDistribution[recipient] = currentDistributionId;
payable(recipient).transfer(amount);
emit IncomeClaimed(recipient, amount);
}
}
}
// Events
event IncomeDeposited(uint256 distributionId, uint256 amount);
event IncomeClaimed(address indexed recipient, uint256 amount);
}
```
### Advantages:
- The contract owner can process distributions in manageable batches
- Token holders don't need to claim manually
- Historical distributions are tracked per token
### Disadvantages:
- Still requires significant gas for large holder bases
- Centralized distribution control
- Needs off-chain coordination for batch processing
## Solution 2: Pull-Based Distribution with Merkle Proofs
For a more gas-efficient approach with 1M+ holders, we can implement a pull-based mechanism using Merkle proofs:
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
contract RealEstateTokenMerkle is ERC20, Ownable {
struct Distribution {
bytes32 merkleRoot;
uint256 totalAmount;
uint256 timestamp;
mapping(address => bool) claimed;
}
mapping(uint256 => Distribution) public distributions;
uint256 public currentDistributionId = 0;
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function createDistribution(bytes32 merkleRoot) external payable onlyOwner {
require(msg.value > 0, "Must deposit funds");
currentDistributionId++;
Distribution storage dist = distributions[currentDistributionId];
dist.merkleRoot = merkleRoot;
dist.totalAmount = msg.value;
dist.timestamp = block.timestamp;
emit DistributionCreated(currentDistributionId, merkleRoot, msg.value);
}
function claim(uint256 distributionId, uint256 amount, bytes32[] calldata merkleProof) external {
Distribution storage dist = distributions[distributionId];
require(dist.timestamp > 0, "Distribution does not exist");
require(!dist.claimed[msg.sender], "Already claimed");
// Verify the merkle proof
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount));
require(MerkleProof.verify(merkleProof, dist.merkleRoot, leaf), "Invalid proof");
// Mark as claimed and transfer
dist.claimed[msg.sender] = true;
payable(msg.sender).transfer(amount);
emit Claimed(distributionId, msg.sender, amount);
}
// Events
event DistributionCreated(uint256 indexed distributionId, bytes32 merkleRoot, uint256 amount);
event Claimed(uint256 indexed distributionId, address indexed account, uint256 amount);
}
```
### Advantages:
- Extremely gas-efficient for large token holder bases
- Each user pays for their own claim transaction
- Scales well to millions of users without chain congestion
- Fully trustless verification
### Disadvantages:
- Requires off-chain computation of the Merkle tree
- Users must manually claim their distributions
- More technical complexity for implementation and user interface
## Comparison
| Aspect | Push-Based Batching | Pull-Based Merkle Proofs |
|--------|---------------------|--------------------------|
| Gas efficiency | Medium - requires multiple transactions for all holders | High - each holder pays for their own claim |
| Scalability | Limited - challenging with 1M+ holders | Excellent - works well with any number of holders |
| User experience | Better - automatic receipt of funds | Requires user action to claim funds |
| Implementation complexity | Moderate | Higher - requires off-chain Merkle tree generation |
| Trustlessness | Medium - relies on centralized distribution | High - cryptographically verified claims |
## Recommendation
For a system with 1M+ token holders, the Merkle proof-based approach is more efficient. While it requires more off-chain computation, the gas savings and scalability benefits make it the superior choice for large-scale distributions.
The optimal solution might combine both approaches:
1. Use Merkle proofs for the majority of smaller token holders (gas-efficient)
2. Implement automatic push distribution for the top X largest holders (better UX)
This hybrid approach would provide gas efficiency while maintaining a good user experience for significant stakeholders.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment