Created
March 9, 2025 07:56
-
-
Save AnirudhMemani/c4de4f634c362c5720dd22eef1534163 to your computer and use it in GitHub Desktop.
Mini technical task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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