Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ Find the up-to-date contract addresses [here][contract-addresses].

## Implementation Table

| Contract | Implemented Interfaces |
| ------------------ | ------------------------------------------------------- |
| `Biomapper` | [`IGenerationChangeEvents`], [`IProveUniquenessEvents`] |
| `BiomapperLog` | [`IBiomapperLogRead`] |
| `BridgedBiomapper` | [`IBridgedBiomapperRead`], [`IBridgeBiomappingEvents`] |
| Contract | Implemented Interfaces |
| ------------------ | ------------------------------------------------------------------------ |
| `Biomapper` | [`IGenerationChangeEvents`], [`IProveUniquenessEvents`] |
| `BiomapperLog` | [`IBiomapperLogRead`], [`IBiomapperLogAddressesPerGenerationEnumerator`] |
| `BridgedBiomapper` | [`IBridgedBiomapperRead`], [`IBridgeBiomappingEvents`] |

[`IBiomapperLogRead`]: core/IBiomapperLogRead.sol/interface.IBiomapperLogRead.html
[`IBridgedBiomapperRead`]: core/IBridgedBiomapperRead.sol/interface.IBridgedBiomapperRead.html
[`IBiomapperLogAddressesPerGenerationEnumerator`]: core/IBiomapperLogAddressesPerGenerationEnumerator.sol/interface.IBiomapperLogAddressesPerGenerationEnumerator.html
[`IGenerationChangeEvents`]: events/IGenerationChangeEvents.sol/interface.IGenerationChangeEvents.html
[`IProveUniquenessEvents`]: events/IProveUniquenessEvents.sol/interface.IProveUniquenessEvents.html
[`IBridgeBiomappingEvents`]: events/IBridgeBiomappingEvents.sol/interface.IBridgeBiomappingEvents.html
Expand All @@ -48,6 +49,7 @@ Import the dependencies from the `@biomapper-sdk` like this:
```solidity
import {IBiomapperLogRead} from "@biomapper-sdk/core/IBiomapperLogRead.sol";
import {IBridgedBiomapperRead} from "@biomapper-sdk/core/IBridgedBiomapperRead.sol";
import {IBiomapperLogAddressesPerGenerationEnumerator} from "@biomapper-sdk/core/IBiomapperLogAddressesPerGenerationEnumerator.sol";
import {BiomapperLogLib} from "@biomapper-sdk/libraries/BiomapperLogLib.sol";
import {BridgedBiomapperLib} from "@biomapper-sdk/libraries/BridgedBiomapperLib.sol";
```
Expand All @@ -65,6 +67,7 @@ Import the dependencies from `biomapper-sdk` like this:
```solidity
import {IBiomapperLogRead} from "biomapper-sdk/core/IBiomapperLogRead.sol";
import {IBridgedBiomapperRead} from "biomapper-sdk/core/IBridgedBiomapperRead.sol";
import {IBiomapperLogAddressesPerGenerationEnumerator} from "biomapper-sdk/core/IBiomapperLogAddressesPerGenerationEnumerator.sol";
import {BiomapperLogLib} from "biomapper-sdk/libraries/BiomapperLogLib.sol";
import {BridgedBiomapperLib} from "biomapper-sdk/libraries/BridgedBiomapperLib.sol";
```
Expand Down
26 changes: 26 additions & 0 deletions core/IBiomapperLogAddressesPerGenerationEnumerator.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IBiomapperLogAddressesPerGenerationEnumerator {
/**
* @dev Retrieves all biomapped accounts within a specified generation.
* @param generationPtr The block number marking the start of the generation to get the list of biomapped accounts.
* @param cursor The starting index of the page. For the first request, set `cursor` to address(0).
* @param maxPageSize The maximum number of elements to return in this call (also soft-capped in the contract).
* @return nextCursor The starting index for the next page of results.
* @return biomappedAccounts An array of addresses that were biomapped within a specified generation.
*
* Notes:
* - For the first request, set `cursor` to address(0) to start from the beginning of the dataset.
* - If `nextCursor` is address(0), all available elements have been retrieved, indicating the end of the dataset.
* - There is a soft cap on the max page size that is implementation-dependent.
*/
function listAddressesPerGeneration(
uint256 generationPtr,
address cursor,
uint256 maxPageSize
)
external
view
returns (address nextCursor, address[] memory biomappedAccounts);
}
2 changes: 1 addition & 1 deletion core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@biomapper-sdk/core",
"version": "0.4.0",
"version": "0.5.0",
"license": "MIT",
"type": "module",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion events/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@biomapper-sdk/events",
"version": "0.4.0",
"version": "0.5.0",
"license": "MIT",
"type": "module",
"files": [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IBiomapperLogRead} from "@biomapper-sdk/core/IBiomapperLogRead.sol";
import {IBiomapperLogAddressesPerGenerationEnumerator} from "@biomapper-sdk/core/IBiomapperLogAddressesPerGenerationEnumerator.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

/**
* @title Proactive Sybil-Resistant Airdrop
* @dev A contract for conducting a Sybil-resistant airdrop by sending tokens to all biomapped users.
*/
contract ProactiveSybilResistantAirdrop {
using SafeERC20 for IERC20;

IERC20 public immutable ERC20_TOKEN; // The ERC20 token being airdropped
address public immutable TOKEN_VAULT; // The address holding the tokens for the airdrop
uint256 public immutable AMOUNT_PER_USER; // The amount of tokens to send to each user
IBiomapperLogAddressesPerGenerationEnumerator
public immutable BIOMAPPER_LOG; // The contract for retrieving unique users list
uint256 public immutable MAX_USERS_PER_AIRDROP_TICK; // Maximum amount of users to get tokens for each function call
uint256 public immutable GENERATION_PTR; // Current generation pointer at the moment of contract deployment

address public nextAccountToGetAirdrop; // The cursor for enumertor
bool public airdropCompleted; // The completion flag

/**
* @dev Constructor to initialize the contract with required parameters.
* @param tokenAddress The address of the ERC20 token being airdropped.
* @param tokenVault The address holding the tokens for the airdrop.
* @param amountPerUser The amount of tokens to send to each user.
* @param biomapperLogAddress The address of the contract for retrieving unique users list.
* @param maxUsersPerAirdropTick The address of the contract for checking uniqueness of users.
*/
constructor(
address tokenAddress,
address tokenVault,
uint256 amountPerUser,
address biomapperLogAddress,
uint256 maxUsersPerAirdropTick
) {
ERC20_TOKEN = IERC20(tokenAddress);
TOKEN_VAULT = tokenVault;
AMOUNT_PER_USER = amountPerUser;
BIOMAPPER_LOG = IBiomapperLogAddressesPerGenerationEnumerator(
biomapperLogAddress
);
MAX_USERS_PER_AIRDROP_TICK = maxUsersPerAirdropTick;
GENERATION_PTR = IBiomapperLogRead(biomapperLogAddress)
.generationsHead();
}

event AirdropIsCompleted();

/**
* @dev Send tokens to biomapped users in the set generation, no more than `MAX_USERS_PER_AIRDROP_TICK` users per call.
* @return needsMoreTicks The list of biomapped accounts is not exhausted, the airdrop is not completed.
*/
function airdropTick() public returns (bool needsMoreTicks) {
require(!airdropCompleted, "Airdrop is completed");

(address nextCursor, address[] memory biomappedAccounts) = BIOMAPPER_LOG
.listAddressesPerGeneration(
GENERATION_PTR,
nextAccountToGetAirdrop,
MAX_USERS_PER_AIRDROP_TICK
);

for (uint index = 0; index < biomappedAccounts.length; index++) {
ERC20_TOKEN.safeTransferFrom(
TOKEN_VAULT,
biomappedAccounts[index],
AMOUNT_PER_USER
);
}

nextAccountToGetAirdrop = nextCursor;

if (nextCursor == address(0)) {
airdropCompleted = true;
emit AirdropIsCompleted();
return false;
}

return true;
}

/**
* @dev Send tokens to all biomapped users in the set generation.
* This function may fail due to excessive gas usage, call `airdropTick` in multiple transactions instead.
*/
function airdrop() external {
require(!airdropCompleted, "Airdrop is completed");

while (airdropTick()) {}
}
}
2 changes: 1 addition & 1 deletion libraries/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@biomapper-sdk/libraries",
"version": "0.4.0",
"version": "0.5.0",
"license": "MIT",
"type": "module",
"files": [
Expand Down
2 changes: 1 addition & 1 deletion mock/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@biomapper-sdk/mock",
"version": "0.4.0",
"version": "0.5.0",
"license": "MIT",
"type": "module",
"files": [
Expand Down