Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add airdrop example #327

Merged
merged 6 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions airdrop/smart-contract/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PRIVATE_KEY=
6 changes: 6 additions & 0 deletions airdrop/smart-contract/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
extends: ['@massalabs'],
rules: {
'no-console': 'off',
},
};
3 changes: 3 additions & 0 deletions airdrop/smart-contract/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules
build
.env
91 changes: 91 additions & 0 deletions airdrop/smart-contract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Execute Airdrop
---
## Overview

`Execute airdrop` is an example designed to demonstrate the use of `executeSc` for executing smart contracts directly on-chain without deployment. This repository showcases an **Airdrop** smart contract, which distributes MRC20 tokens to a predefined list of addresses in a single atomic operation.

The **Airdrop** smart contract leverages `executeSc` to ensure that the operation is atomic — if any transaction fails, all transactions in the airdrop revert, preventing partial token distribution.

---

## Prerequisites

- [Node.js](https://nodejs.org/) (v18+)
- Massa Account
- [Node.js](https://nodejs.org/) (v18+)
- Massa Account
- MRC20 Token Contract (Deployed). Check the [fungible token standard](https://github.com/massalabs/massa-standards/tree/main/smart-contracts/assembly/contracts/FT) for more details.


## Installation

1. Install dependencies:
```bash
npm install
```

2. Configure environment variables by creating a `.env` file:
```plaintext
PRIVATE_KEY=your_private_key
```

3. Update the `addressList` in `addressList.ts` with the recipient addresses for the airdrop.
4. Update MRC20 contract address in `assembly/contracts/mrc20-address.ts` contract.
5. Update token amount in `airdrop.ts` contract.


## Scripts

Build the smart contracts:
```bash
npm run build
```

Execute the Airdrop smart contract:
```bash
npm run airdrop
```

## Smart Contracts

### Airdrop Contract
- Distributes an `amount`of Tokens tokens to each address in the predefined `addressList`.
- Key Features:
- Validates that the airdrop has not been executed previously (version control).
- Ensures the number of recipients does not exceed block limits.
- **Important**:
- **Version Tracking**: Update the `VERSION` constant in the `airdrop.ts` contract file before executing a new airdrop.
- **MRC20 Contract Address**: Replace the placeholder MRC20 address in the `assembly/contracts/mrc20-address.ts` file with the actual deployed MRC20 address.


## Usage

### Executing the Airdrop
1. Ensure the MRC20 tokens are minted and available in your wallet.
2. **Update `airdrop.ts`**:
- Set the correct `VERSION` value if running the airdrop for the first time or after modifications.
- Replace the placeholder MRC20 contract address with the actual one.
3. Run the Airdrop contract:
`npm run airdrop`

4. Check the balances of recipient addresses in `addressList` to verify successful distribution.


## Address List

The recipient addresses for the airdrop are defined in `addressList.ts`. Update this list with your desired addresses before execution. Limited to 850 addresses per airdrop due to block limits.

```typescript
export const addressList = [
'AU1jCffxJFjMQRg1WkoT1gDiFvZGg1WuogiwBWQFZ2LJwEyHPRPZ',
'AU1rFqhTWvRyuJT3UH5ZZSBjALcAsjXCPPcgUkWaBghYsYicTrB5',
// Add more addresses as needed
];
```


## Notes

- **Atomic Execution**: Leveraging `executeSc` ensures that either all transactions succeed, or none are executed, preventing partial token distributions.
- **Version Control**: Always update the `VERSION` in the Airdrop contract to prevent accidental reruns.
- **MRC20 Contract Address**: Ensure the MRC20 contract address is updated in `assembly/contracts/mrc20-address` before executing the airdrop.
24 changes: 24 additions & 0 deletions airdrop/smart-contract/as-pect.asconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"targets": {
"coverage": {
"lib": ["./node_modules/@as-covers/assembly/index.ts"],
"transform": ["@as-covers/transform", "@as-pect/transform"]
},
"noCoverage": {
"transform": ["@as-pect/transform"]
}
},
"options": {
"exportMemory": true,
"outFile": "output.wasm",
"textFile": "output.wat",
"bindings": "raw",
"exportStart": "_start",
"exportRuntime": true,
"use": ["RTRACE=1"],
"debug": true,
"exportTable": true
},
"extends": "./asconfig.json",
"entries": ["./node_modules/@as-pect/assembly/assembly/index.ts"]
}
28 changes: 28 additions & 0 deletions airdrop/smart-contract/as-pect.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import createMockedABI from '@massalabs/massa-as-sdk/vm-mock';

export default {
/**
* A set of globs passed to the glob package that qualify typescript files for testing.
*/
entries: ['assembly/__tests__/**/*.spec.ts'],
/**
* A set of globs passed to the glob package that quality files to be added to each test.
*/
include: ['assembly/__tests__/**/*.include.ts'],
/**
* A set of regexp that will disclude source files from testing.
*/
disclude: [/node_modules/],
/**
* Add your required AssemblyScript imports here.
*/
async instantiate(memory, createImports, instantiate, binary) {
return createMockedABI(memory, createImports, instantiate, binary);
},
/** Enable code coverage. */
// coverage: ["assembly/**/*.ts"],
/**
* Specify if the binary wasm file should be written to the file system.
*/
outputBinary: false,
};
13 changes: 13 additions & 0 deletions airdrop/smart-contract/asconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"targets": {
"release": {
"sourceMap": true,
"optimizeLevel": 3,
"shrinkLevel": 3,
"converge": true,
"noAssert": false,
"exportRuntime": true,
"bindings": false
}
}
}
1 change: 1 addition & 0 deletions airdrop/smart-contract/assembly/__tests__/as-pect.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/// <reference types="@as-pect/assembly/types/as-pect" />
12 changes: 12 additions & 0 deletions airdrop/smart-contract/assembly/const/addressList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const addressList = [
'AU1jCffxJFjMQRg1WkoT1gDiFvZGg1WuogiwBWQFZ2LJwEyHPRPZ',
'AU1rFqhTWvRyuJT3UH5ZZSBjALcAsjXCPPcgUkWaBghYsYicTrB5',
'AU1x44P7LWJfy3w6bL7fKmH8CxXzZq8a1odg3WKUoq6iqvs5Kcwv',
'AU1c3YbxDL5vyaJQ8mGQFBEABmmopMWCdhHQpUxcDkVAU4ya666S',
'AU1SjbSd8u4sutG7fSuxnPgTbiifrHJGN1paopKwDgDkDDTG8iJo',
'AU1HG96jNJXKVyqeEuwiTFMwfZvX2WVTjTTq8yzxcdYbSmW7n2Hx',
'AU1i4ey48VhVbG7YyRJHTZ6mkQTj8NESNCbyf8xDgJWbcEmbX8sk',
'AU12hrZyu1wCYAx8k76J8gu9gNksFzmpCA9ke21bsjV4wG5rpNN1j',
'AU1U41TqgyhTsWcXntev86FGm3gUD4vM7cKALJGCfyE2UJKZvYC9',
'AU1vjHCLS3AVApxm4kUCecuTQJuuZRoUbvsszZabLKZi8WUzAzrX',
];
2 changes: 2 additions & 0 deletions airdrop/smart-contract/assembly/const/mrc20-address.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const mrc20Address =
'AS1adMwkCyNFf1XDYCkFGjP4CAGTeSRvUmnjN3Chw6mpmSsLuRnJ';
59 changes: 59 additions & 0 deletions airdrop/smart-contract/assembly/contracts/airdrop.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Address, generateEvent, Storage } from '@massalabs/massa-as-sdk';
import { bytesToU32, stringToBytes, u32ToBytes } from '@massalabs/as-types';
import { u256 } from 'as-bignum/assembly';
import { addressList } from '../const/addressList';
import { mrc20Address } from '../const/mrc20-address';
import { TokenWrapper } from '@massalabs/sc-standards/assembly/contracts/FT';
import { getBalanceEntryCost } from '@massalabs/sc-standards/assembly/contracts//FT/token-external';

const VERSION_KEY = stringToBytes('airdrop_version'); // Key to store the version of the airdrop
const VERSION = u32(1); // Increment this number to run the airdrop again
const AIRDROP_LIMIT = 850; // Maximum number of transfers per block

export const MRC20_ADDRESS = new Address(mrc20Address);

/** Update this value to the amount you wish to airdrop at each account */
const TOKEN_AMOUNT = 20; // Amount of tokens to airdrop

/**
* Main function to run the airdrop. Will be automatically called when executing the contract.
*/
export function main(_: StaticArray<u8>): void {
checkAndSetAirdropVersion(); // Can be removed if you know what you are doing
validateAddressLimit();

const coin = new TokenWrapper(MRC20_ADDRESS);
const decimals = coin.decimals();

const amount = u256.fromU64(TOKEN_AMOUNT) * u256.fromU64(10 ** decimals);

for (let i = 0; i < addressList.length; i++) {
const cost = getBalanceEntryCost(mrc20Address, addressList[i]);
generateEvent(cost.toString());
coin.transfer(new Address(addressList[i]), amount, cost);
}

generateEvent('Airdrop done');
}

function checkAndSetAirdropVersion(): void {
const previousVersion = Storage.has(VERSION_KEY)
? bytesToU32(Storage.get(VERSION_KEY))
: 0;

assert(
VERSION > previousVersion,
`Airdrop already done for version ${VERSION}, please increase version number to ${
previousVersion + 1
}`,
);

Storage.set(VERSION_KEY, u32ToBytes(VERSION));
}

function validateAddressLimit(): void {
assert(
addressList.length <= AIRDROP_LIMIT,
'Too many addresses. The number of transfers exceeds the block limit.',
);
}
14 changes: 14 additions & 0 deletions airdrop/smart-contract/assembly/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"extends": "assemblyscript/std/assembly.json",
"include": [
"**/*.ts"
],
"typedocOptions": {
"exclude": "assembly/**/__tests__",
"excludePrivate": true,
"excludeProtected": true,
"excludeExternals": true,
"includeVersion": true,
"skipErrorChecking": true
}
}
Loading