diff --git a/smart-contracts/as-pect.config.js b/smart-contracts/as-pect.config.js index 5bc5a6a..5d7ad2e 100644 --- a/smart-contracts/as-pect.config.js +++ b/smart-contracts/as-pect.config.js @@ -4,11 +4,11 @@ export default { /** * A set of globs passed to the glob package that qualify typescript files for testing. */ - entries: ['assembly/**/__tests__/**/*.spec.ts'], + 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'], + include: ['**/assembly/**/__tests__/**/*.include.ts'], /** * A set of regexp that will disclude source files from testing. */ diff --git a/smart-contracts/erc20/assembly/__tests__/erc20.spec.ts b/smart-contracts/erc20/assembly/__tests__/erc20.spec.ts new file mode 100644 index 0000000..9defcec --- /dev/null +++ b/smart-contracts/erc20/assembly/__tests__/erc20.spec.ts @@ -0,0 +1,27 @@ +import { u256 } from "as-bignum/assembly"; +import { Exportable, ERC20 } from ".."; +import { Args } from "@massalabs/as-types"; +import { Address } from "@massalabs/massa-as-sdk"; + +const totalSupply = u256.fromU32(1000); + +// @eslint-ignore @typescript-eslint/no-unused-vars +// @ts-ignore +@lazy @global const FungibleToken = new ERC20(totalSupply); + +describe('ERC20 - Documentation tests', () => { + it('should be simple to use - create smart contract', () => { + + const totalSupplyBinary = new Args().add(totalSupply).serialize(); + const addr1 = new Address('AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKq'); + const addr1Binary = new Args().add(addr1).serialize(); + const addr2 = new Address('AU12UBnqTHDQALpocVBnkPNy7y5CndUJQTLutaVDDFgMJcq5kQiKr'); + const allowanceArgs = new Args().add(addr1).add(addr2).serialize(); + const u256ZeroBinary = new Args().add(u256.Zero).serialize(); + + expect(Exportable.totalSupply()).toStrictEqual(totalSupplyBinary); + expect(Exportable.balanceOf(addr1Binary)).toStrictEqual(u256ZeroBinary); + expect(Exportable.allowance(allowanceArgs)).toStrictEqual(u256ZeroBinary); + }); +}); + diff --git a/smart-contracts/erc20/assembly/connectable/erc20Core.ts b/smart-contracts/erc20/assembly/connectable/erc20Core.ts new file mode 100644 index 0000000..a4a2a4d --- /dev/null +++ b/smart-contracts/erc20/assembly/connectable/erc20Core.ts @@ -0,0 +1,15 @@ +import { Address, call } from "@massalabs/massa-as-sdk"; +import { ERC20Core } from "../interfaces/erc20Core"; +import { u256 } from "as-bignum"; +import { Args } from "@massalabs/as-types"; + +export class ERC20CoreImpl { // implements ERC20Core { + constructor(public sc: Address) {} + + allowance(owner: Address, spender: Address, coins: u64 = 0): u256 { + const args = new Args().add(owner).add(spender); + const response = call(this.sc, 'allowance', args, coins); + return new Args(response).mustNext("allowance"); + } + +} \ No newline at end of file diff --git a/smart-contracts/erc20/assembly/exportable/erc20Core.ts b/smart-contracts/erc20/assembly/exportable/erc20Core.ts new file mode 100644 index 0000000..e7147f5 --- /dev/null +++ b/smart-contracts/erc20/assembly/exportable/erc20Core.ts @@ -0,0 +1,40 @@ +import { Args } from "@massalabs/as-types"; +import { Address } from "@massalabs/massa-as-sdk"; + +export function allowance(_args: StaticArray): StaticArray { + const args = new Args(_args); + const owner = args.mustNext
("owner"); + const spender = args.mustNext
("spender"); + FungibleToken.allowance(owner, spender).serialize(); +} + +export function approve(_args: StaticArray): void{ + const args = new Args(_args); + const spender = args.mustNext
("spender"); + const amount = args.mustNext("amount"); + FungibleToken.approve(spender, amount); +} + +export function balanceOf(_args: StaticArray): StaticArray { + const of = new Args(_args).mustNext
("of"); + return new Args().add(FungibleToken.balanceOf(of)).serialize(); +} + +export function totalSupply(): StaticArray { + return new Args().add(FungibleToken.totalSupply()).serialize(); +} + +export function transfer(_args: StaticArray): void{ + const args = new Args(_args); + const to = args.mustNext
("to"); + const amount = args.mustNext("amount"); + FungibleToken.transfer(to, amount); +} + +export function transferFrom(_args: StaticArray): void{ + const args = new Args(_args); + const from = args.mustNext
("from"); + const to = args.mustNext
("to"); + const amount = args.mustNext("amount"); + FungibleToken.transferFrom(from, to, amount); +} \ No newline at end of file diff --git a/smart-contracts/erc20/assembly/implementation/erc20Full.ts b/smart-contracts/erc20/assembly/implementation/erc20Full.ts new file mode 100644 index 0000000..e0b880b --- /dev/null +++ b/smart-contracts/erc20/assembly/implementation/erc20Full.ts @@ -0,0 +1,69 @@ +import { ERC20Core } from "../interfaces/erc20Core"; +import { Args } from "@massalabs/as-types"; +import { MapManager, + KeySequenceManager, + KeyIncrementer, ConstantManager, Address, Context, createEvent } from "@massalabs/massa-as-sdk"; +import { u256 } from "as-bignum/assembly"; + +export const EVENT_PREFIX = "ERC20"; +export const EVENT_ERR_INSUFFICIENT_BALANCE = "transfer amount exceeds balance"; +export const EVENT_ERR_INSUFFICIENT_ALLOWANCE = "transfer amount exceeds allowance"; +export const EVENT_TRANSFER = "Transfer"; +export const EVENT_APPROVAL = "Approval"; + +@inline +function triggerTransferEvent(from: Address, to: Address, value: u256): void { + createEvent(`${EVENT_PREFIX}: ${EVENT_TRANSFER}`, [from.toString(), to.toString(), value.toString()]); +} + +export class ERC20 implements ERC20Core { + private _totalSupply: ConstantManager; + public balances: MapManager; + public allowances: MapManager, u256>; + + constructor(totalSupply: u256, keyManager: KeySequenceManager = new KeyIncrementer(0)) { + this._totalSupply = new ConstantManager(keyManager); + this.balances = new MapManager(keyManager); + this.allowances = new MapManager, u256>(keyManager); + + this._totalSupply.set(totalSupply); + } + + totalSupply(): u256 { + return this._totalSupply.mustValue(); + } + + balanceOf(address: Address): u256 { + return this.balances.value(address).unwrapOrDefault(); + } + + transfer(to: Address, value: u256): void { + const from = Context.caller(); + const balance = this.balanceOf(from); + assert(balance >= value, `${EVENT_PREFIX}: ${EVENT_ERR_INSUFFICIENT_BALANCE}`); + this.balances.set(from, balance - value); + this.balances.set(to, this.balanceOf(to) + value); + triggerTransferEvent(from, to, value); + } + + transferFrom(from: Address, to: Address, value: u256): void { + const caller = Context.caller(); + const allowance = this.allowance(from, caller); + assert(allowance >= value, `${EVENT_PREFIX}: ${EVENT_ERR_INSUFFICIENT_ALLOWANCE}`); + this.approve(caller, allowance - value); + this.transfer(to, value); + triggerTransferEvent(from, to, value); + } + + approve(spender: Address, value: u256): void { + const from = Context.caller(); + const storageKey = new Args().add(from).add(spender).serialize(); + this.allowances.set(storageKey, value); + createEvent(`${EVENT_PREFIX}: ${EVENT_APPROVAL}`, [from.toString(), spender.toString(), value.toString()]); + } + + allowance(owner: Address, spender: Address): u256 { + const storageKey = new Args().add(owner).add(spender).serialize(); + return this.allowances.value(storageKey).unwrapOrDefault(); + } +} diff --git a/smart-contracts/erc20/assembly/index.ts b/smart-contracts/erc20/assembly/index.ts new file mode 100644 index 0000000..410ec45 --- /dev/null +++ b/smart-contracts/erc20/assembly/index.ts @@ -0,0 +1,6 @@ +export * from './implementation/erc20Full'; +export * from './interfaces/erc20Core'; +import * as Exportable from './exportable/erc20Core'; +export { Exportable }; + +export * from './connectable/erc20Core'; diff --git a/smart-contracts/erc20/assembly/interfaces/erc20Core.ts b/smart-contracts/erc20/assembly/interfaces/erc20Core.ts new file mode 100644 index 0000000..11a3d0e --- /dev/null +++ b/smart-contracts/erc20/assembly/interfaces/erc20Core.ts @@ -0,0 +1,14 @@ +import { u256 } from "as-bignum/assembly"; +import { Address } from "@massalabs/massa-as-sdk"; + +export interface ERC20Core { + allowance(owner: Address, spender: Address): u256; + approve(spender: Address, value: u256): void; + balanceOf(address: Address): u256; + // decimals(): u8; + // name(): string; + // symbol(): string; + totalSupply(): u256; + transfer(to: Address, value: u256): void; + transferFrom(from: Address, to: Address, value: u256): void; +} diff --git a/smart-contracts/erc20/assembly/tsconfig.json b/smart-contracts/erc20/assembly/tsconfig.json new file mode 100644 index 0000000..9ad9d5a --- /dev/null +++ b/smart-contracts/erc20/assembly/tsconfig.json @@ -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 + } +} diff --git a/smart-contracts/erc20/dapp/connector.ts b/smart-contracts/erc20/dapp/connector.ts new file mode 100644 index 0000000..a4883f0 --- /dev/null +++ b/smart-contracts/erc20/dapp/connector.ts @@ -0,0 +1,2 @@ +// todo TS code to connect a DAPP to an ERC20 smart contract +// Similar to assembly/connectable but for TS and from outside of the blockchain. \ No newline at end of file diff --git a/smart-contracts/package.json b/smart-contracts/package.json index 5737fa7..3776634 100644 --- a/smart-contracts/package.json +++ b/smart-contracts/package.json @@ -19,7 +19,7 @@ "devDependencies": { "@as-pect/cli": "^8.0.1", "@assemblyscript/loader": "^0.25.2", - "@massalabs/as-types": "^2.0.0", + "@massalabs/as-types": "^2.0.1-dev", "@massalabs/eslint-config": "^0.0.9", "@massalabs/massa-as-sdk": "^2.5.5-dev", "@massalabs/massa-sc-compiler": "^0.1.1-dev",