Skip to content

Commit

Permalink
fix(signer): fix signer in WarpContracts - update tests
Browse files Browse the repository at this point in the history
  • Loading branch information
dtfiedler committed Mar 27, 2024
1 parent 95cc019 commit 9998063
Show file tree
Hide file tree
Showing 13 changed files with 118 additions and 64 deletions.
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
Loading

0 comments on commit 9998063

Please sign in to comment.