diff --git a/src/chain/tezos/contracts/StakerDaoTzip7.ts b/src/chain/tezos/contracts/StakerDaoTzip7.ts new file mode 100644 index 00000000..78619437 --- /dev/null +++ b/src/chain/tezos/contracts/StakerDaoTzip7.ts @@ -0,0 +1,157 @@ +import { JSONPath } from 'jsonpath-plus'; + +import { KeyStore, Signer } from '../../../types/ExternalInterfaces'; +import * as TezosTypes from '../../../types/tezos/TezosChainTypes'; +import { TezosMessageUtils } from '../TezosMessageUtil'; +import { TezosNodeReader } from '../TezosNodeReader'; +import { TezosNodeWriter } from '../TezosNodeWriter'; +import { TezosContractUtils } from './TezosContractUtils'; + +/** The expected checksum for the StakerDao Tzip 7 contract. */ +const CONTRACT_CHECKSUMS = { + token: 'd48b45bd77d2300026fe617c5ba7670e', +} + +/** The expected checksum for the StakerDao Tzip 7 script. */ +const SCRIPT_CHECKSUMS = { + // TODO(keefertaylor): Compute this checksum correctly. + token: '', +} + +export interface StakerDaoTzip7Storage { + balanceMap: number; + approvalsMap: number; + supply: number; + administrator: string; + paused: boolean; + pauseGuardian: string; + outcomeMap: number; + swapMap: number; +} + +export interface StakerDaoTzip7BalanceRecord { } +export interface StakerDaoTzip7ApprovalRecord { } +export interface StakerDaoTzip7OutcomeRecord { } +export interface StakerDaoTzip7SwapRecord { } + +/** + * Interface for a StakerDAO implementation of TZIP-7, AKA FA 1.2. + * + * @author Keefer Taylor, Staker Services Ltd + */ +export const StakerDaoTzip7 = { + /** + * Verifies that contract code for StakerDao's Tzip7 contract matches the expected code. + * + * Note: This function processes contracts in the Micheline format. + * + * @param nodeUrl The URL of the Tezos node which serves data. + * @param tokenContractAddress The address of the token contract. + * @returns A boolean indicating if the code was the expected sum. + */ + verifyDestination: async function ( + nodeUrl: string, + tokenContractAddress: string + ): Promise { + return await TezosContractUtils.verifyDestination(nodeUrl, tokenContractAddress, CONTRACT_CHECKSUMS.token) + }, + + /** + * Verifies that Michelson script for StakerDao's Tzip7 contract matches the expected code. + * + * Note: This function processes scrips in Michelson format. + * + * @param tokenScript The script of the token contract. + * @returns A boolean indicating if the code was the expected sum. + */ + verifyScript: function ( + tokenScript: string, + ): boolean { + return TezosContractUtils.verifyScript(tokenScript, SCRIPT_CHECKSUMS.token) + }, + + /** + * @param server + * @param address + */ + getSimpleStorage: async function (server: string, address: string): Promise { + const storageResult = await TezosNodeReader.getContractStorage(server, address); + + console.log(JSON.stringify(storageResult)); + + return { + balanceMap: Number(JSONPath({ path: '$.args[1].args[0].args[1].args[0].int', json: storageResult })[0]), + approvalsMap: Number(JSONPath({ path: '$.args[1].args[0].args[0].args[1].int', json: storageResult })[0]), + supply: Number(JSONPath({ path: '$.args[1].args[1].args[1].int', json: storageResult })[0]), + administrator: JSONPath({ path: '$.args[1].args[0].args[0].args[0].string', json: storageResult })[0], + paused: (JSONPath({ path: '$.args[1].args[1].args[0].prim', json: storageResult })[0]).toString().toLowerCase().startsWith('t'), + pauseGuardian: JSONPath({ path: '$.args[1].args[0].args[1].args[1].string', json: storageResult })[0], + outcomeMap: Number(JSONPath({ path: '$.args[0].args[0].int', json: storageResult })[0]), + swapMap: Number(JSONPath({ path: '$.args[0].args[1].int', json: storageResult })[0]) + }; + }, + + /** + * Get the balance of tokens for an address. + * + * @param nodeUrl The URL of the Tezos node which serves data. + * @param mapId The ID of the BigMap which contains balances. + * @param account The account to fetch the token balance for. + * @returns The balance of the account. + */ + getAccountBalance: async function (server: string, mapid: number, account: string): Promise { + const packedKey = TezosMessageUtils.encodeBigMapKey(Buffer.from(TezosMessageUtils.writePackedData(account, 'address'), 'hex')); + const mapResult = await TezosNodeReader.getValueForBigMapKey(server, mapid, packedKey); + + if (mapResult === undefined) { throw new Error(`Map ${mapid} does not contain a record for ${account}`); } + + const numberString = JSONPath({ path: '$.int', json: mapResult }); + return Number(numberString); + }, + + /** + * Transfer some WXTZ between addresses. + * + * @param nodeUrl The URL of the Tezos node which serves data. + * @param signer A Signer for the sourceAddress. + * @param keystore A Keystore for the sourceAddress. + * @param tokenContractAddress The address of the token contract. + * @param fee The fee to use. + * @param sourceAddress The address which will send tokens. + * @param destinationAddress The address which will receive tokens. + * @param amount The amount of tokens to send. + * @param gasLimit The gas limit to use. + * @param storageLimit The storage limit to use. + * @returns A string representing the operation hash. + */ + transferBalance: async function ( + nodeUrl: string, + signer: Signer, + keystore: KeyStore, + tokenContractAddress: string, + fee: number, + sourceAddress: string, + destinationAddress: string, + amount: number, + gasLimit: number = 51_300, + storageLimit: number = 70 + ): Promise { + const parameters = `Pair "${sourceAddress}" (Pair "${destinationAddress}" ${amount})`; + + const nodeResult = await TezosNodeWriter.sendContractInvocationOperation( + nodeUrl, + signer, + keystore, + tokenContractAddress, + 0, + fee, + storageLimit, + gasLimit, + 'transfer', + parameters, + TezosTypes.TezosParameterFormat.Michelson + ); + + return TezosContractUtils.clearRPCOperationGroupHash(nodeResult.operationGroupID); + } +} diff --git a/src/chain/tezos/contracts/WrappedTezosHelper.ts b/src/chain/tezos/contracts/WrappedTezosHelper.ts index 1c369865..9db58c44 100644 --- a/src/chain/tezos/contracts/WrappedTezosHelper.ts +++ b/src/chain/tezos/contracts/WrappedTezosHelper.ts @@ -1,27 +1,24 @@ -import { JSONPath } from 'jsonpath-plus'; import { KeyStore, Signer } from '../../../types/ExternalInterfaces'; import * as TezosTypes from '../../../types/tezos/TezosChainTypes'; import { TezosMessageUtils } from '../TezosMessageUtil'; -import { TezosNodeReader } from '../TezosNodeReader'; import { TezosNodeWriter } from '../TezosNodeWriter'; import { TezosContractUtils } from './TezosContractUtils'; import { TezosConseilClient } from '../../../reporting/tezos/TezosConseilClient' import { ConseilServerInfo } from '../../../types/conseil/QueryTypes'; import { ContractMapDetailsItem } from '../../../types/conseil/ConseilTezosTypes'; import { TezosParameterFormat } from '../../../types/tezos/TezosChainTypes'; +import { StakerDaoTzip7 } from './StakerDaoTzip7'; +import { StakerDAOTokenHelper } from './StakerDAOTokenHelper'; /** The expected checksum for the Wrapped Tezos contracts. */ const CONTRACT_CHECKSUMS = { - token: 'd48b45bd77d2300026fe617c5ba7670e', oven: '5e3c30607da21a0fc30f7be61afb15c7', core: '7b9b5b7e7f0283ff6388eb783e23c452' } /** The expected checksum for the Wrapped Tezos scripts. */ const SCRIPT_CHECKSUMS = { - // TODO(keefertaylor): Compute this checksum correctly. - token: '', // TODO(keefertaylor): Compute this checksum correctly. oven: '', // TODO(keefertaylor): Compute this checksum correctly. @@ -39,22 +36,6 @@ export type OpenOvenResult = { ovenAddress: string } -export interface WrappedTezosStorage { - balanceMap: number; - approvalsMap: number; - supply: number; - administrator: string; - paused: boolean; - pauseGuardian: string; - outcomeMap: number; - swapMap: number; -} - -export interface WrappedTezosBalanceRecord { } -export interface WrappedTezosApprovalRecord { } -export interface WrappedTezosOutcomeRecord { } -export interface WrappedTezosSwapRecord { } - /** * Types for the Oven Map . * @@ -64,24 +45,11 @@ export interface WrappedTezosSwapRecord { } export type OvenMapSchema = { key: string, value: string } /** - * Interface for the Wrapped XTZ Token and Oven implementation. + * Wrapped Tezos specific functions. * - * @see {@link https://forum.tezosagora.org/t/wrapped-tezos/2195|wXTZ on Tezos Agora} - * - * The token represented by these contracts trades with symbol 'WXTZ' and is specified with 10^-6 precision. Put - * simply, 1 XTZ = 1 WXTZ, and 0.000_001 XTZ = 1 Mutez = 0.000_001 WXTZ. - * - * Canonical Data: - * - Delphinet: - * - Token Contract: KT1JYf7xjCJAqFDfNpuump9woSMaapy1WcMY - * - Core Contract: KT1S98ELFTo6mdMBqhAVbGgKAVgLbdPP3AX8 - * - Token Balances Map ID: 14566 - * - Oven List Map ID: 14569 - * TODO(keefertaylor): Add additional data for mainnet here. - * - * @author Keefer Taylor, Staker Services Ltd + * @see {WrappedTezosHelper} */ -export namespace WrappedTezosHelper { +const WrappedTezosHelperInternal = { /** * Verifies that contract code for Wrapped Tezos matches the expected code. * @@ -93,18 +61,16 @@ export namespace WrappedTezosHelper { * @param coreContractAddress The address of the core contract. * @returns A boolean indicating if the code was the expected sum. */ - export async function verifyDestination( + verifyDestination: async function ( nodeUrl: string, - tokenContractAddress: string, ovenContractAddress: string, coreContractAddress: string ): Promise { - const tokenMatched = TezosContractUtils.verifyDestination(nodeUrl, tokenContractAddress, CONTRACT_CHECKSUMS.token) - const ovenMatched = TezosContractUtils.verifyDestination(nodeUrl, ovenContractAddress, CONTRACT_CHECKSUMS.oven) - const coreMatched = TezosContractUtils.verifyDestination(nodeUrl, coreContractAddress, CONTRACT_CHECKSUMS.core) + const ovenMatched = await TezosContractUtils.verifyDestination(nodeUrl, ovenContractAddress, CONTRACT_CHECKSUMS.oven) + const coreMatched = await TezosContractUtils.verifyDestination(nodeUrl, coreContractAddress, CONTRACT_CHECKSUMS.core) - return tokenMatched && ovenMatched && coreMatched - } + return ovenMatched && coreMatched + }, /** * Verifies that Michelson script for Wrapped Tezos contracts matches the expected code. @@ -116,103 +82,15 @@ export namespace WrappedTezosHelper { * @param coreScript The script of the core contract. * @returns A boolean indicating if the code was the expected sum. */ - export function verifyScript( - tokenScript: string, + verifyScript: function ( ovenScript: string, coreScript: string ): boolean { - const tokenMatched = TezosContractUtils.verifyScript(tokenScript, SCRIPT_CHECKSUMS.token) const ovenMatched = TezosContractUtils.verifyScript(ovenScript, SCRIPT_CHECKSUMS.oven) const coreMatched = TezosContractUtils.verifyScript(coreScript, SCRIPT_CHECKSUMS.core) - return tokenMatched && ovenMatched && coreMatched - } - - /** - * - * @param server - * @param address - */ - export async function getSimpleStorage(server: string, address: string): Promise { - const storageResult = await TezosNodeReader.getContractStorage(server, address); - - console.log(JSON.stringify(storageResult)); - - return { - balanceMap: Number(JSONPath({ path: '$.args[1].args[0].args[1].args[0].int', json: storageResult })[0]), - approvalsMap: Number(JSONPath({ path: '$.args[1].args[0].args[0].args[1].int', json: storageResult })[0]), - supply: Number(JSONPath({ path: '$.args[1].args[1].args[1].int', json: storageResult })[0]), - administrator: JSONPath({ path: '$.args[1].args[0].args[0].args[0].string', json: storageResult })[0], - paused: (JSONPath({ path: '$.args[1].args[1].args[0].prim', json: storageResult })[0]).toString().toLowerCase().startsWith('t'), - pauseGuardian: JSONPath({ path: '$.args[1].args[0].args[1].args[1].string', json: storageResult })[0], - outcomeMap: Number(JSONPath({ path: '$.args[0].args[0].int', json: storageResult })[0]), - swapMap: Number(JSONPath({ path: '$.args[0].args[1].int', json: storageResult })[0]) - }; - } - - /** - * Get the balance of WXTZ tokens for an address. - * - * @param nodeUrl The URL of the Tezos node which serves data. - * @param mapId The ID of the BigMap which contains balances. - * @param account The account to fetch the token balance for. - * @returns The balance of the account. - */ - export async function getAccountBalance(server: string, mapid: number, account: string): Promise { - const packedKey = TezosMessageUtils.encodeBigMapKey(Buffer.from(TezosMessageUtils.writePackedData(account, 'address'), 'hex')); - const mapResult = await TezosNodeReader.getValueForBigMapKey(server, mapid, packedKey); - - if (mapResult === undefined) { throw new Error(`Map ${mapid} does not contain a record for ${account}`); } - - const numberString = JSONPath({ path: '$.int', json: mapResult }); - return Number(numberString); - } - - /** - * Transfer some WXTZ between addresses. - * - * @param nodeUrl The URL of the Tezos node which serves data. - * @param signer A Signer for the sourceAddress. - * @param keystore A Keystore for the sourceAddress. - * @param tokenContractAddress The address of the token contract. - * @param fee The fee to use. - * @param sourceAddress The address which will send tokens. - * @param destinationAddress The address which will receive tokens. - * @param amount The amount of tokens to send. - * @param gasLimit The gas limit to use. - * @param storageLimit The storage limit to use. - * @returns A string representing the operation hash. - */ - export async function transferBalance( - nodeUrl: string, - signer: Signer, - keystore: KeyStore, - tokenContractAddress: string, - fee: number, - sourceAddress: string, - destinationAddress: string, - amount: number, - gasLimit: number = 51_300, - storageLimit: number = 70 - ): Promise { - const parameters = `Pair "${sourceAddress}" (Pair "${destinationAddress}" ${amount})`; - - const nodeResult = await TezosNodeWriter.sendContractInvocationOperation( - nodeUrl, - signer, - keystore, - tokenContractAddress, - 0, - fee, - storageLimit, - gasLimit, - 'transfer', - parameters, - TezosTypes.TezosParameterFormat.Michelson - ); - - return TezosContractUtils.clearRPCOperationGroupHash(nodeResult.operationGroupID); - } + return ovenMatched && coreMatched + }, /** * Deposit XTZ into an oven to mint WXTZ. @@ -230,7 +108,7 @@ export namespace WrappedTezosHelper { * @param storageLimit The storage limit to use. * @returns A string representing the operation hash. */ - export async function depositToOven( + depositToOven: async function depositToOven( nodeUrl: string, signer: Signer, keystore: KeyStore, @@ -257,7 +135,7 @@ export namespace WrappedTezosHelper { ) return TezosContractUtils.clearRPCOperationGroupHash(nodeResult.operationGroupID); - } + }, /** * Withdraw XTZ from an oven by repaying WXTZ. @@ -277,7 +155,7 @@ export namespace WrappedTezosHelper { * @param storageLimit The storage limit to use. * @returns A string representing the operation hash. */ - export async function withdrawFromOven( + withdrawFromOven: async function ( nodeUrl: string, signer: Signer, keystore: KeyStore, @@ -304,7 +182,7 @@ export namespace WrappedTezosHelper { ) return TezosContractUtils.clearRPCOperationGroupHash(nodeResult.operationGroupID); - } + }, /** * Retrieve a list of all oven addresses a user owns. @@ -314,7 +192,7 @@ export namespace WrappedTezosHelper { * @param ovenOwner The oven owner to search for * @param ovenListBigMapId The BigMap ID of the oven list. */ - export async function listOvens( + listOven: async function ( serverInfo: ConseilServerInfo, coreContractAddress: string, ovenOwner: string, @@ -358,7 +236,7 @@ export namespace WrappedTezosHelper { return ownedOvens.map((oven: OvenMapSchema) => { return oven.key }) - } + }, /** * Deploy a new oven contract. @@ -411,7 +289,7 @@ export namespace WrappedTezosHelper { operationHash, ovenAddress } - } + }, /** * Set the baker for an oven. @@ -428,7 +306,7 @@ export namespace WrappedTezosHelper { * @param storageLimit The storage limit to use. * @returns A string representing the operation hash. */ - export async function setOvenBaker( + setOvenBaker: async function ( nodeUrl: string, signer: Signer, keystore: KeyStore, @@ -455,8 +333,7 @@ export namespace WrappedTezosHelper { ) return TezosContractUtils.clearRPCOperationGroupHash(nodeResult.operationGroupID); - } - + }, /** * Clear the baker for an oven. @@ -472,7 +349,7 @@ export namespace WrappedTezosHelper { * @param storageLimit The storage limit to use. * @returns A string representing the operation hash. */ - export async function clearOvenBaker( + clearOvenBaker: async function ( nodeUrl: string, signer: Signer, keystore: KeyStore, @@ -500,3 +377,68 @@ export namespace WrappedTezosHelper { return TezosContractUtils.clearRPCOperationGroupHash(nodeResult.operationGroupID); } } + +/** + * Interface for the Wrapped XTZ Token and Oven implementation. + * + * @see {@link https://forum.tezosagora.org/t/wrapped-tezos/2195|wXTZ on Tezos Agora} + * + * The token represented by these contracts trades with symbol 'WXTZ' and is specified with 10^-6 precision. Put + * simply, 1 XTZ = 1 WXTZ, and 0.000_001 XTZ = 1 Mutez = 0.000_001 WXTZ. + * + * Canonical Data: + * - Delphinet: + * - Token Contract: KT1JYf7xjCJAqFDfNpuump9woSMaapy1WcMY + * - Core Contract: KT1S98ELFTo6mdMBqhAVbGgKAVgLbdPP3AX8 + * - Token Balances Map ID: 14566 + * - Oven List Map ID: 14569 + * TODO(keefertaylor): Add additional data for mainnet here. + * + * @author Keefer Taylor, Staker Services Ltd + */ +export const WrappedTezosHelper = StakerDaoTzip7 && WrappedTezosHelperInternal && { + /** + * Verifies that contract code for Wrapped Tezos matches the expected code. + * + * Note: This function processes contracts in the Micheline format. + * + * @param nodeUrl The URL of the Tezos node which serves data. + * @param tokenContractAddress The address of the token contract. + * @param ovenContractAddress The address of an oven contract. + * @param coreContractAddress The address of the core contract. + * @returns A boolean indicating if the code was the expected sum. + */ + verifyDestination: async function verifyDestination( + nodeUrl: string, + tokenContractAddress: string, + ovenContractAddress: string, + coreContractAddress: string + ): Promise { + const tokenMatched = await StakerDaoTzip7.verifyDestination(nodeUrl, tokenContractAddress) + const wrappedTezosInternalMatched = await WrappedTezosHelperInternal.verifyDestination(nodeUrl, ovenContractAddress, coreContractAddress) + + return tokenMatched && wrappedTezosInternalMatched + }, + + /** + * Verifies that Michelson script for Wrapped Tezos contracts matches the expected code. + * + * Note: This function processes scrips in Michelson format. + * + * @param tokenScript The script of the token contract. + * @param ovenScript The script of an oven contract. + * @param coreScript The script of the core contract. + * @returns A boolean indicating if the code was the expected sum. + */ + verifyScript: function ( + tokenScript: string, + ovenScript: string, + coreScript: string + ): boolean { + // Confirm that both interfaces report contracts correctly. + const tokenMatched = StakerDaoTzip7.verifyScript(tokenScript) + const wrappedTezosInternalMatched = WrappedTezosHelperInternal.verifyScript(ovenScript, coreScript) + + return tokenMatched && wrappedTezosInternalMatched + } +} \ No newline at end of file