Skip to content

Commit

Permalink
Merge pull request #771 from lens-protocol/T-17848/support-accessCont…
Browse files Browse the repository at this point in the history
…rolContract

fix: token-gated patch for misconfigured access control contract
  • Loading branch information
cesarenaldi authored Dec 6, 2023
2 parents 6ec00c5 + 913bc91 commit a9f233e
Show file tree
Hide file tree
Showing 16 changed files with 192 additions and 122 deletions.
6 changes: 6 additions & 0 deletions .changeset/wicked-bobcats-joke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@lens-protocol/gated-content": patch
"@lens-protocol/client": patch
---

**fixed:** misconfiguration of Lit Access Control contract address
2 changes: 1 addition & 1 deletion packages/client/src/__helpers__/buildTestEnvironment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ dotenv.config();
export const buildTestEnvironment = (): Environment => {
invariant(process.env.TESTING_ENV_URL, 'TESTING_ENV_URL is not defined in .env file');

return new Environment('testing', process.env.TESTING_ENV_URL, GatedEnvironments.sandbox);
return new Environment('testing', process.env.TESTING_ENV_URL, GatedEnvironments.development);
};
65 changes: 47 additions & 18 deletions packages/gated-content/src/GatedClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ import { SiweMessage } from 'siwe';
import { createAuthStorage } from './AuthStorage';
import { CannotDecryptError } from './CannotDecryptError';
import { ICipher, IEncryptionProvider } from './IEncryptionProvider';
import { DecryptionContext, transformFromGql, transformFromRaw } from './conditions';
import { transformFromGql, transformFromRaw, DecryptionContext } from './conditions';
import { PublicationMetadataDecryptor, PublicationMetadataEncryptor } from './encryption';
import { EnvironmentConfig } from './environments';
import * as gql from './graphql';
import { isLitError } from './types';

function isIncorrectAccessControlConditionsError(error: unknown): boolean {
return isLitError(error) && error.errorCode === 'incorrect_access_control_conditions';
}

/**
* As per [EIP-4361](https://eips.ethereum.org/EIPS/eip-4361) the information
* provided will be used to create a SIWE message.
Expand Down Expand Up @@ -176,11 +180,7 @@ export class GatedClient {
encryptedMetadata: T,
context: DecryptionContext,
): Promise<T> {
const cipher = await this.retrieveCipher(
encryptedMetadata.encryptedWith.encryptionKey,
encryptedMetadata.encryptedWith.accessCondition,
context,
);
const cipher = await this.retrieveCipher(encryptedMetadata, context);

switch (encryptedMetadata.__typename) {
case 'ArticleMetadataV3':
Expand Down Expand Up @@ -248,29 +248,58 @@ export class GatedClient {
authSig,
chain: this.environment.chainName,
symmetricKey,
unifiedAccessControlConditions: transformFromRaw(accessCondition, this.environment),
unifiedAccessControlConditions: transformFromRaw(
accessCondition,
this.environment.accessControlContract,
),
});

return raw.toLitEncryptionKey(uint8arrayToHexString(encryptedSymmetricKey));
}

private async retrieveCipher(
encryptedEncryptionKey: gql.Scalars['ContentEncryptionKey'],
accessCondition: gql.RootCondition,
private async retrieveCipher<T extends gql.EncryptedFragmentOfAnyPublicationMetadata>(
encryptedMetadata: T,
context: DecryptionContext,
): Promise<ICipher> {
const authSig = await this.getOrCreateAuthSig();

const encryptionKey = await this.litClient.getEncryptionKey({
authSig,
chain: this.environment.chainName,
toDecrypt: encryptedEncryptionKey,
unifiedAccessControlConditions: transformFromGql(accessCondition, this.environment, context),
});
const encryptionKey = await this.retrieveEncryptionKey(encryptedMetadata, context);

return this.encryptionProvider.importCipher(encryptionKey);
}

private async retrieveEncryptionKey<T extends gql.EncryptedFragmentOfAnyPublicationMetadata>(
encryptedMetadata: T,
context: DecryptionContext,
): Promise<Uint8Array> {
const authSig = await this.getOrCreateAuthSig();

try {
return await this.litClient.getEncryptionKey({
authSig,
chain: this.environment.chainName,
toDecrypt: encryptedMetadata.encryptedWith.encryptionKey,
unifiedAccessControlConditions: transformFromGql(
encryptedMetadata.encryptedWith,
this.environment.accessControlContract,
context,
),
});
} catch (error: unknown) {
if (isIncorrectAccessControlConditionsError(error) && this.environment.patches) {
return await this.litClient.getEncryptionKey({
authSig,
chain: this.environment.chainName,
toDecrypt: encryptedMetadata.encryptedWith.encryptionKey,
unifiedAccessControlConditions: transformFromGql(
encryptedMetadata.encryptedWith,
this.environment.patches.accessControlContract,
context,
),
});
}
throw error;
}
}

private async createSiweMessage() {
return new SiweMessage({
address: await this.signer.getAddress(),
Expand Down
9 changes: 0 additions & 9 deletions packages/gated-content/src/__helpers__/env.ts

This file was deleted.

12 changes: 12 additions & 0 deletions packages/gated-content/src/__helpers__/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
} from '@lens-protocol/metadata';
import * as mocks from '@lens-protocol/shared-kernel/mocks';

import { AccessControlContract, SupportedChainId } from '../conditions/types';

export function mockPublicationMetadata(): PublicationMetadata {
return {
$schema: PublicationSchemaId.ARTICLE_LATEST,
Expand Down Expand Up @@ -149,3 +151,13 @@ export function mockAdvancedContractCondition(
...overrides,
};
}

export function mockAccessControlContract(
overrides?: Partial<AccessControlContract>,
): AccessControlContract {
return {
address: mockEvmAddress(),
chainId: SupportedChainId.MUMBAI,
...overrides,
};
}
4 changes: 2 additions & 2 deletions packages/gated-content/src/__tests__/GatedClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import { Wallet } from 'ethers';

import { CannotDecryptError } from '../CannotDecryptError';
import { DecryptionContext, GatedClient } from '../GatedClient';
import { testing } from '../__helpers__/env';
import {
mockEoaOwnershipCondition,
mockProfileId,
mockProfileOwnershipCondition,
mockPublicationMetadata,
} from '../__helpers__/mocks';
import { development } from '../environments';
import { EncryptedFragmentOfAnyPublicationMetadata } from '../graphql';
import * as gql from '../graphql/__helpers__/mocks';
import { webCryptoProvider } from '../web';
Expand All @@ -34,7 +34,7 @@ function setupTestScenario() {
uri: 'https://localhost/login',
},
signer,
environment: testing,
environment: development,
storageProvider: new InMemoryStorageProvider(),
encryptionProvider: webCryptoProvider(),
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,42 @@
import { toPublicationId } from '@lens-protocol/metadata';
import { BigNumber } from 'ethers';

import { testing } from '../../__helpers__/env';
import { mockCollectCondition, mockProfileId, mockPublicationId } from '../../__helpers__/mocks';
import {
mockAccessControlContract,
mockCollectCondition,
mockProfileId,
mockPublicationId,
} from '../../__helpers__/mocks';
import { transformCollectCondition } from '../collect-condition';
import {
DecryptionContext,
LitConditionType,
LitKnownMethods,
LitKnownParams,
LitScalarOperator,
SupportedChains,
} from '../types';
import { toLitSupportedChainName } from '../utils';
import { InvalidAccessCriteriaError } from '../validators';

const contract = mockAccessControlContract();

describe(`Given the "${transformCollectCondition.name}" function`, () => {
describe('when called with a Collect Publication condition', () => {
const publicationId = mockPublicationId();

it('should return the expected Lit AccessControlCondition for a given publication Id', () => {
const condition = mockCollectCondition({ publicationId });

const actual = transformCollectCondition(condition, testing);
const actual = transformCollectCondition(condition, contract);

const publicationIdParts = publicationId
.split('-')
.map((part) => BigNumber.from(part).toString());
const expectedLitAccessConditions = [
{
conditionType: LitConditionType.EVM_CONTRACT,
chain: SupportedChains.MUMBAI,
contractAddress: testing.contractAddress,
chain: toLitSupportedChainName(contract.chainId),
contractAddress: contract.address,
functionName: LitKnownMethods.HAS_COLLECTED,
functionParams: [LitKnownParams.USER_ADDRESS, ...publicationIdParts, '0', '0x'],
functionAbi: {
Expand Down Expand Up @@ -89,7 +95,7 @@ describe(`Given the "${transformCollectCondition.name}" function`, () => {
};
const condition = mockCollectCondition({ publicationId });

const actual = transformCollectCondition(condition, testing, context);
const actual = transformCollectCondition(condition, contract, context);

const publicationIdParts = publicationId
.split('-')
Expand Down Expand Up @@ -117,7 +123,7 @@ describe(`Given the "${transformCollectCondition.name}" function`, () => {
])(
`should throw an $expectedErrorCtor.name $description`,
({ condition, expectedErrorCtor }) => {
expect(() => transformCollectCondition(condition, testing)).toThrow(expectedErrorCtor);
expect(() => transformCollectCondition(condition, contract)).toThrow(expectedErrorCtor);
},
);
});
Expand Down
35 changes: 20 additions & 15 deletions packages/gated-content/src/conditions/__tests__/conditions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,24 @@ import {
} from '@lens-protocol/metadata';
import { BigNumber } from 'ethers';

import { testing } from '../../__helpers__/env';
import * as metadata from '../../__helpers__/mocks';
import * as gql from '../../graphql/__helpers__/mocks';
import { DecryptionContext, transformFromGql, transformFromRaw } from '../index';
import { transformFromGql, transformFromRaw } from '../index';
import {
DecryptionContext,
LitConditionType,
LitContractType,
LitKnownMethods,
LitKnownParams,
LitScalarOperator,
SupportedChains,
} from '../types';
import { toLitSupportedChainName } from '../utils';
import { InvalidAccessCriteriaError } from '../validators';

const ownerProfileId = metadata.mockProfileId();
const knownAddress = metadata.mockEvmAddress();
const contract = metadata.mockAccessControlContract();

describe(`Given the conditions helpers`, () => {
describe.each([
Expand All @@ -33,7 +35,7 @@ describe(`Given the conditions helpers`, () => {
metadata.mockProfileOwnershipCondition({ profileId: ownerProfileId }),
metadata.mockEoaOwnershipCondition({ address: knownAddress }),
]),
gql: gql.mockRootCondition({
api: gql.mockRootCondition({
criteria: [
gql.mockProfileOwnershipCondition({ profileId: ownerProfileId }),
gql.mockEoaOwnershipCondition({ address: knownAddress }),
Expand All @@ -42,8 +44,8 @@ describe(`Given the conditions helpers`, () => {
expectedLitAccessConditions: [
{
conditionType: LitConditionType.EVM_CONTRACT,
chain: SupportedChains.MUMBAI,
contractAddress: testing.contractAddress,
chain: toLitSupportedChainName(contract.chainId),
contractAddress: contract.address,
functionName: LitKnownMethods.HAS_ACCESS,
functionParams: [
LitKnownParams.USER_ADDRESS,
Expand Down Expand Up @@ -112,7 +114,7 @@ describe(`Given the conditions helpers`, () => {
}),
]),
]),
gql: gql.mockRootCondition({
api: gql.mockRootCondition({
criteria: [
gql.mockProfileOwnershipCondition({ profileId: ownerProfileId }),
gql.mockOrCondition({
Expand All @@ -128,8 +130,8 @@ describe(`Given the conditions helpers`, () => {
expectedLitAccessConditions: [
{
conditionType: LitConditionType.EVM_CONTRACT,
chain: SupportedChains.MUMBAI,
contractAddress: testing.contractAddress,
chain: toLitSupportedChainName(contract.chainId),
contractAddress: contract.address,
functionName: LitKnownMethods.HAS_ACCESS,
functionParams: [
LitKnownParams.USER_ADDRESS,
Expand Down Expand Up @@ -213,7 +215,7 @@ describe(`Given the conditions helpers`, () => {
}),
]),
]),
gql: gql.mockRootCondition({
api: gql.mockRootCondition({
criteria: [
gql.mockProfileOwnershipCondition({ profileId: ownerProfileId }),
gql.mockAndCondition({
Expand All @@ -229,8 +231,8 @@ describe(`Given the conditions helpers`, () => {
expectedLitAccessConditions: [
{
conditionType: LitConditionType.EVM_CONTRACT,
chain: SupportedChains.MUMBAI,
contractAddress: testing.contractAddress,
chain: toLitSupportedChainName(contract.chainId),
contractAddress: contract.address,
functionName: LitKnownMethods.HAS_ACCESS,
functionParams: [
LitKnownParams.USER_ADDRESS,
Expand Down Expand Up @@ -303,10 +305,10 @@ describe(`Given the conditions helpers`, () => {
],
],
},
])(`and $description`, ({ raw, gql, expectedLitAccessConditions }) => {
])(`and $description`, ({ raw, api, expectedLitAccessConditions }) => {
describe(`when calling "${transformFromRaw.name}"`, () => {
it('should return the expected Lit AccessControlCondition', () => {
const actual = transformFromRaw(raw, testing);
const actual = transformFromRaw(raw, contract);

expect(actual).toMatchObject(expectedLitAccessConditions);
});
Expand All @@ -318,7 +320,10 @@ describe(`Given the conditions helpers`, () => {
};

it('should return the expected Lit AccessControlCondition', () => {
const actual = transformFromGql(gql, testing, context);
const strategy = gql.mockPublicationMetadataLitEncryption({
accessCondition: api,
});
const actual = transformFromGql(strategy, contract, context);

expect(actual).toMatchObject(expectedLitAccessConditions);
});
Expand Down Expand Up @@ -435,7 +440,7 @@ describe(`Given the conditions helpers`, () => {
},
])(`when calling "${transformFromRaw.name}" with $description`, ({ condition }) => {
it(`should throw a ${InvalidAccessCriteriaError.name}`, () => {
expect(() => transformFromRaw(condition as AccessCondition, testing)).toThrow(
expect(() => transformFromRaw(condition as AccessCondition, contract)).toThrow(
InvalidAccessCriteriaError,
);
});
Expand Down
Loading

0 comments on commit a9f233e

Please sign in to comment.