Skip to content

Commit

Permalink
feat: Add logic to handle single and batch erc-1155 transfers
Browse files Browse the repository at this point in the history
  • Loading branch information
brunomenezes committed Feb 18, 2024
1 parent 38037e4 commit 3d0c5df
Show file tree
Hide file tree
Showing 3 changed files with 261 additions and 2 deletions.
69 changes: 67 additions & 2 deletions src/handlers/InputAdded.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
import { BlockData, DataHandlerContext, Log } from '@subsquid/evm-processor';
import { Store } from '@subsquid/typeorm-store';
import { dataSlice, getUint } from 'ethers';
import { AbiCoder, dataSlice, getUint } from 'ethers';
import { Contract as ERC20 } from '../abi/ERC20';
import { Contract as ERC721 } from '../abi/ERC721';
import { events } from '../abi/InputBox';
import {
ERC1155BatchPortalAddress,
ERC1155SinglePortalAddress,
ERC20PortalAddress,
ERC721PortalAddress,
InputBoxAddress,
} from '../config';
import {
Application,
Erc1155Deposit,
Erc1155Transfer,
Erc20Deposit,
Erc721Deposit,
Input,
MultiToken,
NFT,
Token,
} from '../model';
Expand All @@ -24,7 +29,6 @@ const logErrorAndReturnNull =
ctx.log.error(reason);
return null;
};

export default class InputAdded implements Handler {
constructor(
private tokenStorage: Map<String, Token>,
Expand All @@ -33,6 +37,8 @@ export default class InputAdded implements Handler {
private inputStorage: Map<String, Input>,
private nftStorage: Map<String, NFT>,
private erc721DepositStorage: Map<String, Erc721Deposit>,
private multiTokenStorage: Map<String, MultiToken>,
private erc1155DepositStorage: Map<String, Erc1155Deposit>,
) {}

private async prepareErc20Deposit(
Expand Down Expand Up @@ -114,6 +120,63 @@ export default class InputAdded implements Handler {
return deposit;
}

private async prepareErc1155Deposit(
input: Input,
block: BlockData,
ctx: DataHandlerContext<Store>,
opts: {
inputId: String;
},
) {
if (
input.msgSender !== ERC1155BatchPortalAddress &&
input.msgSender !== ERC1155SinglePortalAddress
)
return undefined;

const tokenAddress = dataSlice(input.payload, 0, 20).toLowerCase(); // 20 bytes for token address
const from = dataSlice(input.payload, 20, 40).toLowerCase(); // 20 bytes for from address
let transfers: Erc1155Transfer[] = [];

if (input.msgSender === ERC1155BatchPortalAddress) {
ctx.log.info(`${input.id} (ERC-1155) batch deposit`);
const data = dataSlice(input.payload, 40); // Data arbitrary size
const [tokenIds, amounts] = new AbiCoder().decode(
['uint256[]', 'uint256[]'],
data,
);
transfers = tokenIds.map(
(tokenIndex: bigint, idx: number) =>
new Erc1155Transfer({ tokenIndex, amount: amounts[idx] }),
);
} else if (input.msgSender === ERC1155SinglePortalAddress) {
ctx.log.info(`${input.id} (ERC-1155) single deposit`);
const tokenIndex = getUint(dataSlice(input.payload, 40, 72)); // 32 bytes for tokenId
const amount = getUint(dataSlice(input.payload, 72, 104)); // 32 bytes for value a.k.a amount
transfers = [new Erc1155Transfer({ tokenIndex, amount })];
}

let token = this.multiTokenStorage.get(tokenAddress);

if (!token) {
token = new MultiToken({ id: tokenAddress });
this.multiTokenStorage.set(tokenAddress, token);
ctx.log.info(`${tokenAddress} (ERC-1155) contract stored.`);
}

const deposit = new Erc1155Deposit({
id: input.id,
from,
token,
transfers,
});

this.erc1155DepositStorage.set(input.id, deposit);
ctx.log.info(`${input.id} (Erc1155Deposit) stored`);

return deposit;
}

async handle(log: Log, block: BlockData, ctx: DataHandlerContext<Store>) {
if (
log.address === InputBoxAddress &&
Expand Down Expand Up @@ -156,6 +219,8 @@ export default class InputAdded implements Handler {

input.erc721Deposit = await this.prepareErc721Deposit(...params);

input.erc1155Deposit = await this.prepareErc1155Deposit(...params);

this.inputStorage.set(inputId, input);
ctx.log.info(`${inputId} (Input) stored`);
}
Expand Down
116 changes: 116 additions & 0 deletions tests/handlers/InputAdded.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,20 @@ import { Contract as ERC721 } from '../../src/abi/ERC721';
import InputAdded from '../../src/handlers/InputAdded';
import {
Application,
Erc1155Deposit,
Erc1155Transfer,
Erc20Deposit,
Erc721Deposit,
Input,
MultiToken,
NFT,
Token,
} from '../../src/model';
import {
block,
ctx,
logErc1155BatchTransfer,
logErc1155SingleTransfer,
logErc20Transfer,
logErc721Transfer,
logs,
Expand All @@ -30,13 +35,20 @@ vi.mock('../../src/model/', async () => {
const Input = vi.fn();
const Erc721Deposit = vi.fn();
const NFT = vi.fn();
const Erc1155Deposit = vi.fn();
const MultiToken = vi.fn();
const Erc1155Transfer = vi.fn();

return {
Application,
Token,
Erc20Deposit,
Erc721Deposit,
Input,
NFT,
MultiToken,
Erc1155Deposit,
Erc1155Transfer,
};
});

Expand All @@ -48,6 +60,9 @@ const ERC721DepositStub = vi.mocked(Erc721Deposit);
const ERC20Mock = vi.mocked(ERC20, true);
const ERC20DepositStub = vi.mocked(Erc20Deposit);
const TokenStub = vi.mocked(Token);
const MultiTokenStub = vi.mocked(MultiToken);
const ERC1155DepositStub = vi.mocked(Erc1155Deposit);
const ERC1155TransferStub = vi.mocked(Erc1155Transfer);

describe('InputAdded', () => {
let inputAdded: InputAdded;
Expand All @@ -57,6 +72,8 @@ describe('InputAdded', () => {
const mockApplicationStorage = new Map();
const mockNftStorage = new Map();
const mockErc721DepositStorage = new Map();
const mockMultiTokenStorage = new Map<string, MultiToken>();
const mockErc1155DepositStorage = new Map<string, Erc1155Deposit>();

beforeEach(() => {
inputAdded = new InputAdded(
Expand All @@ -66,6 +83,8 @@ describe('InputAdded', () => {
mockInputStorage,
mockNftStorage,
mockErc721DepositStorage,
mockMultiTokenStorage,
mockErc1155DepositStorage,
);

mockTokenStorage.clear();
Expand All @@ -74,6 +93,8 @@ describe('InputAdded', () => {
mockInputStorage.clear();
mockNftStorage.clear();
mockErc721DepositStorage.clear();
mockMultiTokenStorage.clear();
mockErc1155DepositStorage.clear();
});

afterEach(() => {
Expand Down Expand Up @@ -282,5 +303,100 @@ describe('InputAdded', () => {
});
});
});

describe('ERC-1155 deposits', () => {
const tokenAddress = '0x2960f4db2b0993ae5b59bc4a0f5ec7a1767e905e';

beforeEach(() => {
// Returning simple object as the Class type for assertion
InputMock.mockImplementationOnce((args) => {
return { ...args } as Input;
});

MultiTokenStub.mockImplementationOnce((args) => {
return { ...args } as MultiToken;
});

ERC1155TransferStub.mockImplementation((args) => {
return { ...args } as Erc1155Transfer;
});

ERC1155DepositStub.mockImplementationOnce((args) => {
return { ...args } as Erc1155Deposit;
});
});

afterEach(() => {
vi.clearAllMocks();
});

test('should store the token information', async () => {
expect(mockMultiTokenStorage.size).toBe(0);

await inputAdded.handle(logErc1155SingleTransfer, block, ctx);

expect(mockMultiTokenStorage.size).toBe(1);
const token = mockMultiTokenStorage.get(tokenAddress);
expect(token?.id).toEqual(tokenAddress);
});

test('should store the deposit information for single transfer', async () => {
const inputId =
'0x4ca2f6935200b9a782a78f408f640f17b29809d8-783';
expect(mockErc1155DepositStorage.size).toBe(0);
await inputAdded.handle(logErc1155SingleTransfer, block, ctx);

expect(mockErc1155DepositStorage.size).toBe(1);

const deposit = mockErc1155DepositStorage.get(inputId);

expect(deposit).toEqual({
from: '0xa074683b5be015f053b5dceb064c41fc9d11b6e5',
id: inputId,
token: {
id: '0x2960f4db2b0993ae5b59bc4a0f5ec7a1767e905e',
},
transfers: [
{
amount: 10000000n,
tokenIndex: 2n,
},
],
});
});

test('should store the deposit information for batch transfer', async () => {
const inputId =
'0x4ca2f6935200b9a782a78f408f640f17b29809d8-784';
expect(mockErc1155DepositStorage.size).toBe(0);
await inputAdded.handle(logErc1155BatchTransfer, block, ctx);

expect(mockErc1155DepositStorage.size).toBe(1);

const deposit = mockErc1155DepositStorage.get(inputId);

expect(deposit).toEqual({
from: '0xa074683b5be015f053b5dceb064c41fc9d11b6e5',
id: inputId,
token: {
id: '0x2960f4db2b0993ae5b59bc4a0f5ec7a1767e905e',
},
transfers: [
{
amount: 100n,
tokenIndex: 0n,
},
{
amount: 1000n,
tokenIndex: 1n,
},
{
amount: 10000n,
tokenIndex: 2n,
},
],
});
});
});
});
});
Loading

0 comments on commit 3d0c5df

Please sign in to comment.