Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(signer): fix signer in WarpContracts - update tests #32

Merged
merged 1 commit into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions jest.config.ts → jest.config.mjs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
module.exports = {
export default {
preset: 'ts-jest',
clearMocks: true,
moduleFileExtensions: ['ts', 'js', 'mjs'],
testMatch: ['**/src/**/*.test.ts', '**/tests/**/*.test.ts'],
testMatch: ['**/src/**/*.test.ts'],
collectCoverage: true,
collectCoverageFrom: ['src/**/*.ts', 'tests/**/*.ts'],
testEnvironment: 'node',
testTimeout: 120_000,
extensionsToTreatAsEsm: ['.ts'],
passWithNoTests: true,
transform: {
'^.+\\.(ts|js)$': ['ts-jest', { useESM: true }],
},
Expand Down
2 changes: 1 addition & 1 deletion jest.integration.config.ts → jest.integration.config.mjs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
export default {
preset: 'ts-jest',
setupFiles: ['./tests/integration/jest.setup.ts'],
globalTeardown: './tests/integration/jest.teardown.ts',
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@
"build:esm": "yarn tsc -p tsconfig.json",
"build:cjs": "yarn tsc -p tsconfig.cjs.json && echo \"{\\\"type\\\": \\\"commonjs\\\"}\" > lib/cjs/package.json",
"build": "yarn clean && yarn build:web && yarn build:esm && yarn build:cjs",
"clean": "rimraf [ lib coverage bundles ]",
"clean": "rimraf [ lib coverage bundles tests/contracts tests/wallets ]",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"format": "prettier --check .",
"format:fix": "prettier --write .",
"test": "yarn clean && jest .",
"test:integration": "docker compose up -d && yarn test --config=jest.integration.config.ts && docker compose down",
"test": "yarn test:unit && yarn test:integration",
"test:unit": "yarn clean && jest --config=jest.config.mjs",
"test:integration": "yarn clean && docker compose up -d && jest --config=jest.integration.config.mjs && docker compose down",
"prepare": "husky install",
"example:mjs": "yarn build:esm && node examples/node/index.mjs",
"example:cjs": "yarn build:cjs && node examples/node/index.cjs",
Expand Down Expand Up @@ -117,7 +118,7 @@
"axios": "1.4.0",
"setimmediate": "^1.0.5",
"warp-arbundles": "^1.0.4",
"warp-contracts": "^1.4.38",
"warp-contracts": "1.4.39",
"warp-contracts-plugin-deploy": "^1.0.13",
"winston": "^3.11.0"
},
Expand Down
69 changes: 38 additions & 31 deletions src/common/contracts/warp-contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Arweave from 'arweave';
import { DataItem, Signer } from 'warp-arbundles';
import { DataItem } from 'warp-arbundles';
import {
Contract,
CustomSignature,
InteractionResult,
LoggerFactory,
Signature,
Transaction,
Warp,
WarpFactory,
defaultCacheOptions,
} from 'warp-contracts';

import { defaultArweave } from '../../constants.js';
import { defaultWarp } from '../../constants.js';
import {
BaseContract,
ContractSigner,
Expand All @@ -36,6 +36,7 @@ import {
WriteContract,
WriteParameters,
} from '../../types.js';
import { sha256B64Url, toB64Url } from '../../utils/base64.js';
import { getContractManifest } from '../../utils/smartweave.js';
import { FailedRequestError, WriteInteractionError } from '../error.js';
import { DefaultLogger } from '../logger.js';
Expand All @@ -50,23 +51,17 @@ export class WarpContract<T>
private contract: Contract<T>;
private contractTxId: string;
private cacheUrl: string | undefined;
private arweave: Arweave;
private log: Logger;
private signer: ContractSigner | undefined;
private logger: Logger;
private warp: Warp;
// warp compatible signer that uses ContractSigner
private signer: CustomSignature | undefined;

constructor({
contractTxId,
cacheUrl,
warp = WarpFactory.forMainnet(
{
...defaultCacheOptions,
inMemory: true, // default to in memory for now, a custom warp implementation can be provided
},
true,
),
warp = defaultWarp,
signer,
arweave = defaultArweave,
log = new DefaultLogger({
logger = new DefaultLogger({
level: 'debug',
}),
}: {
Expand All @@ -75,13 +70,13 @@ export class WarpContract<T>
warp?: Warp;
signer?: ContractSigner;
arweave?: Arweave;
log?: Logger;
logger?: Logger;
}) {
this.contractTxId = contractTxId;
this.contract = warp.contract<T>(contractTxId);
this.contract = warp.contract(contractTxId);
this.cacheUrl = cacheUrl;
this.arweave = arweave;
this.log = log;
this.warp = warp;
this.logger = logger;
if (signer) {
this.connect(signer);
}
Expand All @@ -94,13 +89,25 @@ export class WarpContract<T>
};
}

// base contract methods
connect(signer: ContractSigner) {
// TODO: Update type to use Signer interface
this.signer = signer;
this.contract = this.contract.connect(signer as Signer);
const warpSigner = new Signature(this.warp, {
signer: async (tx: Transaction) => {
const dataToSign = await tx.getSignatureData();
const signatureBuffer = Buffer.from(await signer.sign(dataToSign));
const id = sha256B64Url(signatureBuffer);
tx.setSignature({
id: id,
owner: toB64Url(signer.publicKey),
signature: toB64Url(signatureBuffer),
});
},
type: 'arweave',
});
this.contract = this.contract.connect(warpSigner);
this.signer = warpSigner;
return this;
}

async getState({ evaluationOptions = {} }: EvaluationParameters): Promise<T> {
await this.ensureContractInit();
const evalTo = evaluationOptions?.evalTo;
Expand All @@ -120,16 +127,16 @@ export class WarpContract<T>
}

async ensureContractInit(): Promise<void> {
this.log.debug(`Checking contract initialized`, {
this.logger.debug(`Checking contract initialized`, {
contractTxId: this.contractTxId,
});

// Get contact manifest and sync state
this.log.debug(`Fetching contract manifest`, {
this.logger.debug(`Fetching contract manifest`, {
contractTxId: this.contractTxId,
});
const { evaluationOptions = {} } = await getContractManifest({
arweave: this.arweave,
arweave: this.warp.arweave,
contractTxId: this.contractTxId,
});
this.contract.setEvaluationOptions(evaluationOptions);
Expand All @@ -138,7 +145,7 @@ export class WarpContract<T>

private async syncState() {
if (this.cacheUrl !== undefined) {
this.log.debug(`Syncing contract state`, {
this.logger.debug(`Syncing contract state`, {
contractTxId: this.contractTxId,
remoteCacheUrl: this.cacheUrl,
});
Expand Down Expand Up @@ -179,7 +186,7 @@ export class WarpContract<T>
async writeInteraction<Input>({
functionName,
inputs,
dryWrite = true,
dryWrite = false,
}: EvaluationParameters<WriteParameters<Input>>): Promise<
Transaction | DataItem | InteractionResult<unknown, unknown>
> {
Expand All @@ -189,7 +196,7 @@ export class WarpContract<T>
'Contract not connected - call .connect(signer) to connect a signer for write interactions ',
);
}
this.log.debug(`Write interaction: ${functionName}`, {
this.logger.debug(`Write interaction: ${functionName}`, {
contractTxId: this.contractTxId,
});
// Sync state before writing
Expand All @@ -207,7 +214,7 @@ export class WarpContract<T>
}

if (dryWrite) {
this.log.debug(`Dry write interaction successful`, {
this.logger.debug(`Dry write interaction successful`, {
contractTxId: this.contractTxId,
functionName,
});
Expand Down
10 changes: 10 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import Arweave from 'arweave';
import { WarpFactory, defaultCacheOptions } from 'warp-contracts';

export const ARWEAVE_TX_REGEX = new RegExp('^[a-zA-Z0-9_-]{43}$');
// sortkey: padded blockheight to 12, JS timestamp, hash of transactionID + block hash. Timestamp only applicable to L2 and normally is all zeros.
Expand All @@ -32,3 +33,12 @@ export const defaultArweave = Arweave.init({
port: 443,
protocol: 'https',
});

export const defaultWarp = WarpFactory.forMainnet(
{
...defaultCacheOptions,
inMemory: true,
},
true,
defaultArweave,
);
37 changes: 37 additions & 0 deletions src/utils/base64.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright (C) 2022-2024 Permanent Data Solutions, Inc. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { bufferTob64Url } from 'arweave/node/lib/utils.js';
import { createHash } from 'crypto';

export function fromB64Url(input: string): Buffer {
const paddingLength = input.length % 4 === 0 ? 0 : 4 - (input.length % 4);

const base64 = input
.replace(/-/g, '+')
.replace(/_/g, '/')
.concat('='.repeat(paddingLength));

return Buffer.from(base64, 'base64');
}

export function toB64Url(buffer: Buffer): string {
return bufferTob64Url(buffer);
}

export function sha256B64Url(input: Buffer): string {
return toB64Url(createHash('sha256').update(input).digest());
}
17 changes: 5 additions & 12 deletions tests/integration/ant.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ArweaveSigner } from 'arbundles';

import { ANT } from '../../src/common/ant';
import { RemoteContract } from '../../src/common/contracts/remote-contract';
import { DefaultLogger } from '../../src/common/logger';
import { ANTState } from '../../src/contract-state';
import {
arweave,
Expand All @@ -12,26 +13,19 @@ import {
const contractTxId = 'UC2zwawQoTnh0TNd9mYLQS4wObBBeaOU5LPQTNETqA4';
const localCacheUrl = `https://api.arns.app`;
describe('ANT contract apis', () => {
const signer = new ArweaveSigner(JSON.parse(process.env.PRIMARY_WALLET_JWK!));
const ant = new ANT({
signer,
contract: new RemoteContract<ANTState>({
cacheUrl: localCacheUrl,
contractTxId,
logger: new DefaultLogger({ level: 'none' }),
}),
});

it('should connect and return a valid instance', async () => {
const jwk = await arweave.wallets.generate();
const signer = new ArweaveSigner(jwk);
const connectAnt = new ANT({
contract: new RemoteContract<ANTState>({
cacheUrl: localCacheUrl,
contractTxId,
}),
});
expect(connectAnt.connect(signer)).toBeDefined();
expect(connectAnt).toBeInstanceOf(ANT);
expect(ant.connect(signer)).toBeDefined();
expect(ant).toBeInstanceOf(ANT);
});

it.each([
Expand Down Expand Up @@ -147,10 +141,9 @@ describe('ANT contract apis', () => {
},
);

it('Should get state with warp contract', async () => {
it('should get state with warp contract', async () => {
const jwk = await arweave.wallets.generate();
const signer = new ArweaveSigner(jwk);
// connecting updates contract to use warp
ant.connect(signer);
const state = await ant.getState();
expect(state).toBeDefined();
Expand Down
2 changes: 2 additions & 0 deletions tests/integration/ar-io.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ArweaveSigner } from 'arbundles';

import { ArIO } from '../../src/common/ar-io.js';
import { RemoteContract } from '../../src/common/contracts/remote-contract.js';
import { DefaultLogger } from '../../src/common/logger.js';
import { ARNS_DEVNET_REGISTRY_TX } from '../../src/constants.js';
import { ArIOState } from '../../src/contract-state.js';
import {
Expand All @@ -20,6 +21,7 @@ describe('ArIO Client', () => {
contract: new RemoteContract<ArIOState>({
cacheUrl: localCacheUrl,
contractTxId,
logger: new DefaultLogger({ level: 'none' }),
}),
});

Expand Down
1 change: 0 additions & 1 deletion tests/integration/arlocal/ant-contract/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1136,7 +1136,6 @@ var setTicker = async (state, { caller, input }) => {
const controllers = state.controllers;
const { ticker } = input;
if (!validateSetTicker(input)) {
console.log(input);
throw new ContractError(INVALID_INPUT_MESSAGE);
}
if (caller !== owner && !controllers.includes(caller)) {
Expand Down
4 changes: 2 additions & 2 deletions tests/integration/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ async function jestGlobalSetup() {

// deploy example any contract
const [arIOContractDeploy, antContractDeploy] = await Promise.all([
deployArIOContract({ jwk: wallet, warp }),
deployANTContract({ jwk: wallet, warp }),
deployArIOContract({ jwk: wallet, address, warp }),
deployANTContract({ jwk: wallet, address, warp }),
]);

// set in the environment
Expand Down
6 changes: 4 additions & 2 deletions tests/integration/warp-contract.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { ArweaveSigner } from 'arbundles';
import { Transaction } from 'warp-contracts';
import Transaction from 'arweave/node/lib/transaction';

import { WarpContract } from '../../src/common/contracts/warp-contract';
import { WriteInteractionError } from '../../src/common/error';
import { DefaultLogger } from '../../src/common/logger';
import { ANTState } from '../../src/contract-state';
import { arweave, localCacheUrl, warp } from '../constants';

Expand All @@ -18,6 +19,7 @@ describe('warp-contract client', () => {
cacheUrl: localCacheUrl,
contractTxId,
warp,
logger: new DefaultLogger({ level: 'none' }),
});
});

Expand All @@ -38,7 +40,6 @@ describe('warp-contract client', () => {
console.error(e);
return e;
});

expect(tx).toBeDefined();
expect(tx).toBeInstanceOf(Transaction);
});
Expand All @@ -48,6 +49,7 @@ describe('warp-contract client', () => {
cacheUrl: localCacheUrl,
contractTxId,
arweave,
logger: new DefaultLogger({ level: 'none' }),
}).connect(signer);

const error = await contract
Expand Down
Loading