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

draft smart contract #24

Merged
merged 17 commits into from
Jul 18, 2024
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
}
4 changes: 1 addition & 3 deletions smart-contract/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
WALLET_SECRET_KEY=""

JSON_RPC_URL_PUBLIC=https://buildnet.massa.net/api/v2:33035
PRIVATE_KEY=
14 changes: 4 additions & 10 deletions smart-contract/asconfig.json
Thykof marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,14 @@
"targets": {
"debug": {
"sourceMap": true,
"debug": true,
"transform": ["@massalabs/as-transformer"]
"debug": true
},
"release": {
"sourceMap": true,
"optimizeLevel": 3,
"shrinkLevel": 0,
"converge": false,
"noAssert": false,
"transform": ["@massalabs/as-transformer"]
"shrinkLevel": 3,
"converge": true,
"noAssert": false
}
},
"options": {
"exportRuntime": true,
"bindings": "esm"
}
}
124 changes: 124 additions & 0 deletions smart-contract/assembly/Schedule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { Args, Serializable, Result } from '@massalabs/as-types';
import { createEvent } from '@massalabs/massa-as-sdk';
import { u256 } from 'as-bignum/assembly';

export class Schedule implements Serializable {
constructor(
public id: u64 = 0,
public tokenAddress: string = '',
public spender: string = '',
public recipient: string = '',
public amount: u256 = u256.Zero,
public interval: u64 = 0,
public occurrences: u64 = 0,
public remaining: u64 = 0,
public tolerance: u32 = 0,
public history: Transfer[] = [],
) {}

serialize(): StaticArray<u8> {
return new Args()
.add(this.id)
.add(this.tokenAddress)
.add(this.spender)
.add(this.recipient)
.add(this.amount)
.add(this.interval)
.add(this.occurrences)
.add(this.remaining)
.add(this.tolerance)
.addSerializableObjectArray(this.history)
.serialize();
}

deserialize(data: StaticArray<u8>, offset: i32): Result<i32> {
const args = new Args(data, offset);
const resultId = args.nextU64();
if (resultId.isErr()) {
return new Result(0, "Can't deserialize id.");
}
const resultTokenAddress = args.nextString();
if (resultTokenAddress.isErr()) {
return new Result(0, "Can't deserialize tokenAddress.");
}
const resultSpender = args.nextString();
if (resultSpender.isErr()) {
return new Result(0, "Can't deserialize spender.");
}
const resultRecipient = args.nextString();
if (resultRecipient.isErr()) {
return new Result(0, "Can't deserialize recipient.");
}
const resultAmount = args.nextU256();
if (resultAmount.isErr()) {
return new Result(0, "Can't deserialize amount.");
}
const resultInterval = args.nextU64();
if (resultInterval.isErr()) {
return new Result(0, "Can't deserialize interval.");
}
const resultOccurrences = args.nextU64();
if (resultOccurrences.isErr()) {
return new Result(0, "Can't deserialize occurrences.");
}
const resultRemaining = args.nextU64();
if (resultRemaining.isErr()) {
return new Result(0, "Can't deserialize times.");
}
const resultTolerance = args.nextU32();
if (resultTolerance.isErr()) {
return new Result(0, "Can't deserialize tolerance.");
}
const resultHistory = args.nextSerializableObjectArray<Transfer>();
if (resultHistory.isErr()) {
return new Result(0, "Can't deserialize history.");
}

this.id = resultId.unwrap();
this.tokenAddress = resultTokenAddress.unwrap();
this.spender = resultSpender.unwrap();
this.recipient = resultRecipient.unwrap();
this.amount = resultAmount.unwrap();
this.interval = resultInterval.unwrap();
this.occurrences = resultOccurrences.unwrap();
this.remaining = resultRemaining.unwrap();
this.tolerance = resultTolerance.unwrap();
this.history = resultHistory.unwrap();

return new Result(args.offset);
}

public createTransferEvent(period: u64, thread: u8): string {
return createEvent('Transfer', [
this.id.toString(),
this.remaining.toString(),
period.toString(),
thread.toString(),
]);
}
}

export class Transfer implements Serializable {
constructor(public period: u64 = 0, public thread: u8 = 0) {}

serialize(): StaticArray<u8> {
return new Args().add(this.period).add(this.thread).serialize();
}

deserialize(data: StaticArray<u8>, offset: i32): Result<i32> {
const args = new Args(data, offset);
const resultPeriod = args.nextU64();
if (resultPeriod.isErr()) {
return new Result(0, "Can't deserialize period.");
}
const resultThread = args.nextU8();
if (resultThread.isErr()) {
return new Result(0, "Can't deserialize thread.");
}

this.period = resultPeriod.unwrap();
this.thread = resultThread.unwrap();

return new Result(args.offset);
}
}
1 change: 1 addition & 0 deletions 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" />
49 changes: 49 additions & 0 deletions smart-contract/assembly/__tests__/tests.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getSchedulesBySpender, startScheduleSendFT } from '../contracts/main';
import { Args, u256ToBytes } from '@massalabs/as-types';
import { mockScCall } from '@massalabs/massa-as-sdk';
import { u256 } from 'as-bignum/assembly';
import { Schedule } from '../Schedule';

describe('Scheduler app test', () => {
test('startScheduleSendFT', () => {
const tokenAddress =
'AS12LpYyAjYRJfYhyu7fkrS224gMdvFHVEeVWoeHZzMdhis7UZ3Eb';
const spender = 'AU12NT6c6oiYQhcXNAPRRqDudZGurJkFKcYNLPYSwYkMoEniHv8FW';
const recipient = 'AU12W92UyGW4Bd94BPniTq4Ra5yhiv6RvjazV2G9Q9GyekYkgqbme';
const amount = u256.fromU64(12345678910);
const interval: u64 = 60;
const occurrences: u64 = 10;
const tolerance: u32 = 5;

// mock allowance
mockScCall(u256ToBytes(amount));

const params = new Args()
.add(tokenAddress)
.add(spender)
.add(recipient)
.add(amount)
.add(interval)
.add(occurrences)
.add(tolerance);

startScheduleSendFT(params.serialize());

const schedulesSer = getSchedulesBySpender(
new Args().add(spender).serialize(),
);

const schedules = new Args(schedulesSer)
.nextSerializableObjectArray<Schedule>()
.unwrap();
expect(schedules.length).toBe(1);
expect(schedules[0].tokenAddress).toBe(tokenAddress);
expect(schedules[0].spender).toBe(spender);
expect(schedules[0].recipient).toBe(recipient);
expect(schedules[0].amount).toBe(amount);
expect(schedules[0].interval).toBe(interval);
expect(schedules[0].occurrences).toBe(occurrences);
expect(schedules[0].remaining).toBe(occurrences);
expect(schedules[0].tolerance).toBe(tolerance);
});
});
110 changes: 110 additions & 0 deletions smart-contract/assembly/contracts/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// The entry file of your WebAssembly module.
import { Address, Context, Storage } from '@massalabs/massa-as-sdk';
import { Args, u64ToBytes } from '@massalabs/as-types';
import { u256 } from 'as-bignum/assembly';
import { Schedule } from '../Schedule';
import {
checkAllowance,
scheduleAllSendFT,
pushSchedule,
readSchedulesBySpender,
removeSchedule,
idCounterKey,
readSchedule,
} from '../internal';

export { asyncSendFT } from '../internal';

/**
* This function is meant to be called only one time: when the contract is deployed.
*
* @param binaryArgs - Arguments serialized with Args
*/
export function constructor(_: StaticArray<u8>): StaticArray<u8> {
// This line is important. It ensures that this function can't be called in the future.
// If you remove this check, someone could call your constructor function and reset your smart contract.
if (!Context.isDeployingContract()) {
return [];
}

// TODO: initialize ownership
Thykof marked this conversation as resolved.
Show resolved Hide resolved

Storage.set(idCounterKey, u64ToBytes(0));
return [];
}

// Write

export function startScheduleSendFT(binaryArgs: StaticArray<u8>): void {
const args = new Args(binaryArgs);
const tokenAddress = args
.nextString()
.expect('Token address is missing or invalid');
const spender = args
.nextString()
.expect('Spender address is missing or invalid');
const recipient = args
.nextString()
.expect('Recipient address is missing or invalid');
const amount = args.nextU256().expect('Amount is missing or invalid');
const interval = args.nextU64().expect('Interval is missing or invalid');
const occurrences = args.nextU64().expect('Times is missing or invalid');
// tolerance is the number of periods to define the range of the schedule execution
let tolerance = args.nextU32().unwrapOrDefault();
if (tolerance === 0) {
tolerance = 10;
}
// @ts-ignore
// TODO: check overflow ? (or use safeMath)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO

checkAllowance(tokenAddress, spender, amount * u256.fromU64(occurrences));

const schedule = new Schedule(
Thykof marked this conversation as resolved.
Show resolved Hide resolved
0,
tokenAddress,
spender,
recipient,
amount,
interval,
occurrences,
occurrences,
tolerance,
);

scheduleAllSendFT(schedule);

pushSchedule(schedule);
}

export function cancelScheduleSendFT(binaryArgs: StaticArray<u8>): void {
const args = new Args(binaryArgs);
const spender = args
.nextString()
.expect('Spender address is missing or invalid');
const id = args.nextU64().expect('Id is missing or invalid');
assert(Context.caller() === new Address(spender), 'Unauthorized');

removeSchedule(spender, id);
}

// Read

export function getSchedulesBySpender(
binaryArgs: StaticArray<u8>,
): StaticArray<u8> {
const args = new Args(binaryArgs);
const spender = args
.nextString()
.expect('Spender address is missing or invalid');

return readSchedulesBySpender(spender);
}

export function getSchedule(binaryArgs: StaticArray<u8>): StaticArray<u8> {
const args = new Args(binaryArgs);
const spender = args
.nextString()
.expect('Spender address is missing or invalid');
const id = args.nextU64().expect('Id is missing or invalid');

return readSchedule(spender, id);
}
Loading