Skip to content

Commit

Permalink
feat: generic token wrapping service
Browse files Browse the repository at this point in the history
  • Loading branch information
grothem committed Oct 25, 2023
1 parent b091f85 commit 63ddea3
Show file tree
Hide file tree
Showing 4 changed files with 1,173 additions and 0 deletions.
158 changes: 158 additions & 0 deletions packages/contract-helpers/src/token-wrapper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { Signature, SignatureLike, splitSignature } from '@ethersproject/bytes';
import { BigNumber, PopulatedTransaction, providers } from 'ethers';
import BaseService from '../commons/BaseService';
import {
BaseTokenWrapper,
BaseTokenWrapperInterface,
IBaseTokenWrapper,
} from './typechain/TokenWrapper';
import { BaseTokenWrapper__factory } from './typechain/TokenWrapper_factory';

interface SupplyTokenWithPermitParams {
amount: string;
onBehalfOf: string;
referralCode: string;
deadline: string;
signature: SignatureLike;
}

export interface TokenWrapperServiceInterface {
getTokenInForTokenOut: (amount: string) => Promise<BigNumber>;
getTokenOutForTokenIn: (amount: string) => Promise<BigNumber>;
supplyToken: (
amount: string,
onBehalfOf: string,
referralCode: string,
) => PopulatedTransaction;
supplyTokenWithPermit: ({
amount,
onBehalfOf,
referralCode,
deadline,
signature,
}: SupplyTokenWithPermitParams) => PopulatedTransaction;
withdrawToken: (amount: string, user: string) => PopulatedTransaction;
withdrawTokenWithPermit: (
amount: string,
user: string,
deadline: string,
signature: SignatureLike,
) => PopulatedTransaction;
}

export class TokenWrapperService
extends BaseService<BaseTokenWrapper>
implements TokenWrapperServiceInterface
{
readonly tokenWrapperAddress: string;
readonly contractInterface: BaseTokenWrapperInterface;

private readonly _contract: BaseTokenWrapper;

constructor(provider: providers.Provider, tokenWrapperAddress: string) {
super(provider, BaseTokenWrapper__factory);
this.tokenWrapperAddress = tokenWrapperAddress;
this.contractInterface = BaseTokenWrapper__factory.createInterface();
this._contract = BaseTokenWrapper__factory.connect(
tokenWrapperAddress,
provider,
);

this.getTokenInForTokenOut = this.getTokenInForTokenOut.bind(this);
this.getTokenOutForTokenIn = this.getTokenOutForTokenIn.bind(this);
this.supplyToken = this.supplyToken.bind(this);
this.supplyTokenWithPermit = this.supplyTokenWithPermit.bind(this);
this.withdrawToken = this.withdrawToken.bind(this);
this.withdrawTokenWithPermit = this.withdrawTokenWithPermit.bind(this);
}

public async getTokenInForTokenOut(amount: string): Promise<BigNumber> {
return this._contract.getTokenInForTokenOut(amount);
}

public async getTokenOutForTokenIn(amount: string): Promise<BigNumber> {
return this._contract.getTokenOutForTokenIn(amount);
}

public supplyToken(amount: string, onBehalfOf: string, referralCode: string) {
const data = this.contractInterface.encodeFunctionData('supplyToken', [
amount,
onBehalfOf,
referralCode,
]);

return {
to: this.tokenWrapperAddress,
from: onBehalfOf,
data,
};
}

public supplyTokenWithPermit({
amount,
onBehalfOf,
referralCode,
deadline,
signature,
}: SupplyTokenWithPermitParams) {
const sig: Signature = splitSignature(signature);

const permitStruct: IBaseTokenWrapper.PermitSignatureStruct = {
deadline,
v: sig.v,
r: sig.r,
s: sig.s,
};

const data = this.contractInterface.encodeFunctionData(
'supplyTokenWithPermit',
[amount, onBehalfOf, referralCode, permitStruct],
);

return {
to: this.tokenWrapperAddress,
from: onBehalfOf,
data,
};
}

public withdrawToken(amount: string, user: string) {
const data = this.contractInterface.encodeFunctionData('withdrawToken', [
amount,
user,
]);

return {
to: this.tokenWrapperAddress,
from: user,
data,
};
}

public withdrawTokenWithPermit(
amount: string,
user: string,
deadline: string,
signature: SignatureLike,
) {
const sig: Signature = splitSignature(signature);

const permitStruct: IBaseTokenWrapper.PermitSignatureStruct = {
deadline,
v: sig.v,
r: sig.r,
s: sig.s,
};

const data = this.contractInterface.encodeFunctionData(
'withdrawTokenWithPermit',
[amount, user, permitStruct],
);

return {
to: this.tokenWrapperAddress,
from: user,
data,
};
}
}
99 changes: 99 additions & 0 deletions packages/contract-helpers/src/token-wrapper/token-wrapper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { providers } from 'ethers';
import { TokenWrapperService } from './index';

describe('SavingsDaiTokenWrapperService', () => {
const tokenWrapperAddress = '0x0000000000000000000000000000000000000001';
const provider = new providers.JsonRpcProvider();
describe('Initialization', () => {
it('should initialize SavingsDaiTokenWrapperService', () => {
expect(
() => new TokenWrapperService(provider, tokenWrapperAddress),
).not.toThrow();
});
it('generates supply token tx', () => {
const onBehalfOf = '0x0000000000000000000000000000000000000004';
const tokenWrapperService = new TokenWrapperService(
provider,
tokenWrapperAddress,
);
const tx = tokenWrapperService.supplyToken(
'10000000000000000000',
onBehalfOf,
'0',
);
expect(tx.to).toEqual(tokenWrapperAddress);
expect(tx.from).toEqual(onBehalfOf);
expect(tx.data).toEqual(
'0x1461e2160000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000',
);
});
it('generates supply with permit tx', () => {
const onBehalfOf = '0x0000000000000000000000000000000000000004';
const tokenWrapperService = new TokenWrapperService(
provider,
tokenWrapperAddress,
);
const tx = tokenWrapperService.supplyTokenWithPermit({
amount: '10000000000000000000',
onBehalfOf,
referralCode: '0',
deadline: '10000',
signature:
'0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c',
});
expect(tx.to).toEqual(tokenWrapperAddress);
expect(tx.from).toEqual(onBehalfOf);
expect(tx.data).toEqual(
'0x4f4663a10000000000000000000000000000000000000000000000008ac7230489e80000000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4',
);
});
it('generates withdraw token tx', () => {
const user = '0x0000000000000000000000000000000000000004';
const tokenWrapperService = new TokenWrapperService(
provider,
tokenWrapperAddress,
);
const tx = tokenWrapperService.withdrawToken(
'10000000000000000000',
user,
);
expect(tx.to).toEqual(tokenWrapperAddress);
expect(tx.from).toEqual(user);
expect(tx.data).toEqual(
'0xbe4b17720000000000000000000000000000000000000000000000008ac7230489e800000000000000000000000000000000000000000000000000000000000000000004',
);
});
it('generates withdraw token with permit tx', () => {
const user = '0x0000000000000000000000000000000000000004';
const tokenWrapperService = new TokenWrapperService(
provider,
tokenWrapperAddress,
);
const tx = tokenWrapperService.withdrawTokenWithPermit(
'10000000000000000000',
user,
'10000',
'0x532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb41c',
);
expect(tx.to).toEqual(tokenWrapperAddress);
expect(tx.from).toEqual(user);
expect(tx.data).toEqual(
'0xe94875ca0000000000000000000000000000000000000000000000008ac7230489e8000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000002710000000000000000000000000000000000000000000000000000000000000001c532f8df4e2502bd869fb35e9301156f9b307380afdcc25cfbc87b2e939f16f7e47c326dc26eb918d327358797ee67ad7415d871ef7eaf0d4f6352d3ad021fbb4',
);
});
});
describe('get token preview', () => {
it('should not throw for token in', async () => {
const service = new TokenWrapperService(provider, tokenWrapperAddress);
await expect(
service.getTokenInForTokenOut('10000000000000000000'),
).rejects.toThrow();
});
it('should not throw for token out', async () => {
const service = new TokenWrapperService(provider, tokenWrapperAddress);
await expect(
service.getTokenOutForTokenIn('10000000000000000000'),
).rejects.toThrow();
});
});
});
Loading

0 comments on commit 63ddea3

Please sign in to comment.