From 4a76353e5b726c8c4d2281b8be047e4a189e5a5d Mon Sep 17 00:00:00 2001 From: CoviloMilos Date: Wed, 22 Feb 2023 16:24:34 +0100 Subject: [PATCH] feat: offset fungible --- src/abi/Registry_ABI.json | 55 +++++++++++++++++++ src/modules/offset.ts | 73 ++++++++++++++++++++++++-- src/modules/unwrap.ts | 14 ++--- src/types/IBaseTokenManagerContract.ts | 2 + src/types/index.ts | 2 +- src/utils/consts.ts | 7 +-- src/utils/theaError.ts | 3 +- 7 files changed, 139 insertions(+), 17 deletions(-) diff --git a/src/abi/Registry_ABI.json b/src/abi/Registry_ABI.json index 006e3f8..3719e87 100644 --- a/src/abi/Registry_ABI.json +++ b/src/abi/Registry_ABI.json @@ -136,6 +136,61 @@ ], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "vintage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "requestRetireFungible", + "outputs": [ + { + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "requestId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "contract BaseERC20", + "name": "baseToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "maker", + "type": "address" + } + ], + "name": "RetireFungibleRequested", + "type": "event" } ] } diff --git a/src/modules/offset.ts b/src/modules/offset.ts index 94fb884..9a42c73 100644 --- a/src/modules/offset.ts +++ b/src/modules/offset.ts @@ -1,14 +1,21 @@ -import { IRegistryContract, ProviderOrSigner, TheaNetwork } from "../types"; -import { amountShouldBeGTZero, consts, ContractWrapper, signerRequired } from "../utils"; +import { IBaseTokenManagerContract, IRegistryContract, ProviderOrSigner, RequestId, TheaNetwork } from "../types"; +import { amountShouldBeGTZero, consts, ContractWrapper, Events, getAddress, signerRequired, TheaError } from "../utils"; import Registry_ABI from "../abi/Registry_ABI.json"; -import { BigNumberish } from "@ethersproject/bignumber"; -import { approve, checkBalance, execute } from "./shared"; +import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; +import { approve, checkBalance, execute, executeWithResponse, TheaERC20 } from "./shared"; import { Signer } from "@ethersproject/abstract-signer"; -import { ContractReceipt } from "@ethersproject/contracts"; +import { Contract, ContractReceipt, Event } from "@ethersproject/contracts"; +import BaseTokenManager_ABI from "../abi/BaseTokenManager_ABI.json"; export class Offset extends ContractWrapper { + readonly baseTokenManager: IBaseTokenManagerContract; constructor(readonly providerOrSigner: ProviderOrSigner, readonly network: TheaNetwork) { super(providerOrSigner, Registry_ABI, consts[`${network}`].registryContract); + this.baseTokenManager = new Contract( + consts[`${network}`].baseTokenManagerContract, + BaseTokenManager_ABI.abi, + providerOrSigner + ) as IBaseTokenManagerContract; } /** @@ -29,4 +36,60 @@ export class Offset extends ContractWrapper { return execute(this.contract.retire(tokenId, amount), { ...this.contractDetails, contractFunction: "retire" }); } + + /** + * Stores a request to retire with NBT token of `amount`, locks the NBT tokens and emits event. + * Backend listens to event and process the request. Tokens are not transferred until backend calls `closeRetireFungible` + * function after processing and validating the recovery and retire of VCC is successful. + * @param vintage - vintage of NBT token + * @param amount - amount of NBT token to retire + * @returns RequestId & ContractReceipt {@link RequestId} + */ + async offsetFungible(vintage: number, amount: BigNumberish): Promise { + signerRequired(this.providerOrSigner); + amountShouldBeGTZero(amount); + + const address = await this.getBaseTokenAddressByVintage(vintage); + + const token = new TheaERC20(this.providerOrSigner, address); + const owner = await getAddress(this.providerOrSigner as Signer); + + // Check balance + await token.checkERC20Balance(owner, amount); + + // Approve + await token.approveERC20(owner, this.contractDetails.address, amount); + + return executeWithResponse( + this.contract.requestRetireFungible(vintage, amount), + { + ...this.contractDetails, + contractFunction: "requestRetireFungible" + }, + this.extractRequestIdFromEvent + ); + } + + /** + * Callback function to extract request ID from the `UnwrapRequested` event + * @param events + * @returns {@link RequestId} + */ + extractRequestIdFromEvent(events?: Event[]): RequestId { + const response: RequestId = { requestId: undefined }; + if (events) { + const event = events.find((event) => event.event === Events.retireOffset); + if (event) response.requestId = event.args?.requestId.toString(); + } + + return response; + } + + private async getBaseTokenAddressByVintage(vintage: number): Promise { + const address = await this.baseTokenManager.baseTokens(vintage); + if (BigNumber.from(address).isZero()) + throw new TheaError({ type: "TOKEN_NOT_FOUND", message: `Token by ${vintage} vintage not found` }); + + return address; + } } diff --git a/src/modules/unwrap.ts b/src/modules/unwrap.ts index e48d3c8..5dc8cc0 100644 --- a/src/modules/unwrap.ts +++ b/src/modules/unwrap.ts @@ -1,4 +1,4 @@ -import { ProviderOrSigner, IRegistryContract, UnwrapTokenState, UnwrapRequestId, TheaNetwork } from "../types"; +import { ProviderOrSigner, IRegistryContract, UnwrapTokenState, RequestId, TheaNetwork } from "../types"; import { consts, ContractWrapper, Events, signerRequired, TheaError, tokenAmountShouldBeTon } from "../utils"; import Registry_ABI from "../abi/Registry_ABI.json"; import { BigNumber, BigNumberish } from "@ethersproject/bignumber"; @@ -17,13 +17,13 @@ export class Unwrap extends ContractWrapper { * @param tokenId id of the VCC token * @param amount amount of tokens to unwrap * @param offchainAccount offchain account to transfer the tokens to - * @returns UnwrapRequestId&ContractReceipt {@link UnwrapRequestId} + * @returns RequestId & ContractReceipt {@link RequestId} */ async unwrapToken( tokenId: BigNumberish, amount: BigNumberish, offchainAccount: string - ): Promise { + ): Promise { signerRequired(this.providerOrSigner); tokenAmountShouldBeTon(amount); @@ -34,7 +34,7 @@ export class Unwrap extends ContractWrapper { spender: this.contractDetails.address }); - return executeWithResponse( + return executeWithResponse( this.contract.unwrap(tokenId, amount, offchainAccount), { ...this.contractDetails, @@ -77,10 +77,10 @@ export class Unwrap extends ContractWrapper { /** * Callback function to extract request ID from the `UnwrapRequested` event * @param events - * @returns {@link UnwrapRequestId} + * @returns {@link RequestId} */ - extractRequestIdFromEvent(events?: Event[]): UnwrapRequestId { - const response: UnwrapRequestId = { requestId: undefined }; + extractRequestIdFromEvent(events?: Event[]): RequestId { + const response: RequestId = { requestId: undefined }; if (events) { const event = events.find((event) => event.event === Events.unwrap); if (event) response.requestId = event.args?.requestId.toString(); diff --git a/src/types/IBaseTokenManagerContract.ts b/src/types/IBaseTokenManagerContract.ts index 55a16c1..543f371 100644 --- a/src/types/IBaseTokenManagerContract.ts +++ b/src/types/IBaseTokenManagerContract.ts @@ -22,4 +22,6 @@ export interface IBaseTokenManagerContract extends Contract { ): Promise; baseCharacteristics(): Promise; + + baseTokens(arg0: PromiseOrValue): Promise; } diff --git a/src/types/index.ts b/src/types/index.ts index f9b2a35..a651241 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -57,7 +57,7 @@ export type SwapOptions = { deadline?: number; // Unix timestamp recipient?: string; }; -export type UnwrapRequestId = { requestId?: string }; +export type RequestId = { requestId?: string }; export enum TokenizationStatus { IN_QUEUE = "IN_QUEUE", diff --git a/src/utils/consts.ts b/src/utils/consts.ts index 8bfb261..233f5de 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -49,7 +49,8 @@ export enum Events { unwrap = "UnwrapRequested", convert = "Converted", recover = "Recovered", - rollTokens = "Rolled" + rollTokens = "Rolled", + retireOffset = "RetireFungibleRequested" } export type EnvConfig = { @@ -73,13 +74,13 @@ export type EnvConfig = { export const consts: { [key in TheaNetwork]: EnvConfig } = { [TheaNetwork.GANACHE]: { networkName: "GANACHE", - registryContract: "0xe135783649BfA7c9c4c6F8E528C7f56166efC8a6", + registryContract: "0x686AfD6e502A81D2e77f2e038A23C0dEf4949A20", theaERC1155Contract: "0x2E1f232a9439C3D459FcEca0BeEf13acc8259Dd8", vintageTokenContract: "0x686AfD6e502A81D2e77f2e038A23C0dEf4949A20", sdgTokenContract: "0x43D1F9096674B5722D359B6402381816d5B22F28", ratingTokenContract: "0x4261D524bc701dA4AC49339e5F8b299977045eA5", currentNbtTokenContract: "", // Call setCurrentNBTContractAddress to set address at init/thea.ts - baseTokenManagerContract: "0xE100c4ffFF7c00253BA4A2a695F5ac909d756D76", + baseTokenManagerContract: "0x95C8f889701f20b624875a5188bEbDc9289b4F51", baseTokenManagerDeployerContract: "0x3ace09bba3b8507681146252d3dd33cd4e2d4f63", stableTokenContract: "0x6B175474E89094C44Da98b954EedeAC495271d0F", //DAI quoterContract: "0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6", diff --git a/src/utils/theaError.ts b/src/utils/theaError.ts index 937b6f4..8805318 100644 --- a/src/utils/theaError.ts +++ b/src/utils/theaError.ts @@ -36,7 +36,8 @@ export type ErrorType = | "INVALID_CHUNK_SIZE" | "SIGNER_REQUIRES_PROVIDER" | "MISSING_CURRENT_NBT_CONTRACT_ADDRESSS" - | "SUBGRAPH_CALL_ERROR"; + | "SUBGRAPH_CALL_ERROR" + | "TOKEN_NOT_FOUND"; export type ErrorProps = { type: ErrorType;