Skip to content

Commit

Permalink
Make AirnodeRrpV0 and RequesterAuthorizerWithErc721 addresses optional (
Browse files Browse the repository at this point in the history
#1755)

* Default to deployed AirnodeRrpV0 if contracts omitted from config

* Add @api3/airnode-protocol as @api3/airnode-validator dependency

* Remove cyclic dependency between packages

airnode-validator depends on airnode-utilities solely for the logger in
one script

* Replace superRefine with transform

* Default to deployed RequesterAuthorizerWithErc721 address when omitted

* Fix type inference in transforms

---------

Co-authored-by: Emanuel Tesar <[email protected]>
Co-authored-by: Michal Kimle <[email protected]>
  • Loading branch information
3 people authored May 26, 2023
1 parent f1f6cc7 commit deb1b35
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 22 deletions.
5 changes: 5 additions & 0 deletions .changeset/selfish-experts-agree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@api3/airnode-validator': minor
---

Make AirnodeRrpV0 and RequesterAuthorizerWithErc721 addresses optional
1 change: 0 additions & 1 deletion packages/airnode-protocol/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
},
"dependencies": {
"@api3/airnode-protocol-v1": "^2.4.0",
"@api3/airnode-utilities": "^0.11.0",
"@openzeppelin/contracts": "4.4.2",
"ethers": "^5.7.2"
}
Expand Down
1 change: 0 additions & 1 deletion packages/airnode-protocol/scripts/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,5 @@
"compilerOptions": {
"noEmit": true
},
"references": [{ "path": "../../airnode-utilities/src" }],
"include": ["./**/*.ts"]
}
10 changes: 6 additions & 4 deletions packages/airnode-protocol/scripts/verify-local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import * as path from 'path';
import * as fs from 'fs';
import { assert } from 'console';
import { logger } from '@api3/airnode-utilities';
import { go } from '@api3/promise-utils';
import { contractNames } from './contract-names';
const hre = require('hardhat');
Expand All @@ -24,7 +23,8 @@ const verifyDeployedBytecode = (
// The compiler appends by default the IPFS hash of the metadata file to the end of the bytecode (the last 53 bytes/106 char)
// for reference: https://docs.soliditylang.org/en/v0.8.17/metadata.html#encoding-of-the-metadata-hash-in-the-bytecode
else if (deployedBytecode.slice(0, -106) === generatedBytecode.slice(0, -106)) {
logger.log(`⚠️ ${contractName} metadata is different from onchain value on ${networkName}!`);
// eslint-disable-next-line no-console
console.log(`⚠️ ${contractName} metadata is different from onchain value on ${networkName}!`);
return true;
} else {
return false;
Expand Down Expand Up @@ -136,7 +136,8 @@ async function main() {
})
);

verificationPromises.forEach((verificationPromise) => logger.log(verificationPromise.data));
// eslint-disable-next-line no-console
verificationPromises.forEach((verificationPromise) => console.log(verificationPromise.data));

if (verificationPromises.some((verificationPromise) => !verificationPromise.success)) {
throw new Error('❗ Some deployments do not match the local build!');
Expand All @@ -146,6 +147,7 @@ async function main() {
main()
.then(() => process.exit(0))
.catch((error) => {
logger.error(error);
// eslint-disable-next-line no-console
console.log(error);
process.exit(1);
});
1 change: 1 addition & 0 deletions packages/airnode-protocol/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export {
mocks,
authorizers,
networks,
references,
PROTOCOL_IDS,
erc721Mocks,
};
Expand Down
1 change: 1 addition & 0 deletions packages/airnode-validator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"test:e2e:update-snapshot": "yarn test:e2e --updateSnapshot"
},
"dependencies": {
"@api3/airnode-protocol": "^0.11.0",
"@api3/ois": "2.0.0",
"@api3/promise-utils": "^0.4.0",
"dotenv": "^16.0.3",
Expand Down
153 changes: 153 additions & 0 deletions packages/airnode-validator/src/config/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { readFileSync } from 'fs';
import { join } from 'path';
import { ZodError } from 'zod';
import zip from 'lodash/zip';
import { references } from '@api3/airnode-protocol';
import {
Config,
configSchema,
Expand All @@ -14,10 +15,15 @@ import {
gasPriceOracleSchema,
localOrCloudProviderSchema,
chainConfigSchema,
crossChainRequesterAuthorizerSchema,
crossChainRequesterAuthorizersWithErc721Schema,
} from './config';
import { version as packageVersion } from '../../package.json';
import { SchemaType } from '../types';

const AirnodeRrpV0Addresses: { [chainId: string]: string } = references.AirnodeRrpV0;
const RequesterAuthorizerWithErc721Addresses: { [chainId: string]: string } = references.RequesterAuthorizerWithErc721;

it('successfully parses config.json', () => {
const config = JSON.parse(
readFileSync(join(__dirname, '../../test/fixtures/interpolated-config.valid.json')).toString()
Expand Down Expand Up @@ -642,6 +648,69 @@ describe('chainConfigSchema', () => {
});
});

describe('ensureValidAirnodeRrp', () => {
const config = JSON.parse(
readFileSync(join(__dirname, '../../test/fixtures/interpolated-config.valid.json')).toString()
);

// The field used to specify chain ID varies by schema
[
{ testName: 'chains', schema: chainConfigSchema, configObject: { ...config.chains[0] }, chainIdField: 'id' },
{
testName: 'crossChainRequesterAuthorizer',
schema: crossChainRequesterAuthorizerSchema,
configObject: { ...config.chains[0].authorizers.crossChainRequesterAuthorizers[0] },
chainIdField: 'chainId',
},
].forEach((obj) => {
const { testName, schema, configObject, chainIdField } = obj;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { contracts, ...objectMissingContracts } = configObject;

it(`fails if AirnodeRrp contract address within ${testName}.contracts is not specified and there is no deployment for the chain`, () => {
const idWithoutDeployment = '99999999999999999999999';
const unknownChain = {
...objectMissingContracts,
[chainIdField]: idWithoutDeployment,
};

expect(() => schema.parse(unknownChain)).toThrow(
new ZodError([
{
code: 'custom',
message:
`AirnodeRrp contract address must be specified for chain ID '${idWithoutDeployment}'` +
`as there was no deployment for this chain exported from @api3/airnode-protocol`,
path: ['contracts'],
},
])
);
});

it(`adds ${testName}.contracts object if the AirnodeRrp contract address is not specified but there is a deployment for the chain`, () => {
const idWithDeployment = '1';
const chainWithDeployment = {
...objectMissingContracts,
[chainIdField]: idWithDeployment,
};
const parsed = schema.parse(chainWithDeployment);
expect(parsed.contracts).toEqual({
AirnodeRrp: AirnodeRrpV0Addresses[chainWithDeployment[chainIdField]],
});
});

it(`allows an AirnodeRrp contract address to be specified within ${testName}.contracts`, () => {
const chainWithContracts = {
...objectMissingContracts,
contracts: {
AirnodeRrp: '0x5FbDB2315678afecb367f032d93F642f64180aa3',
},
};
expect(() => schema.parse(chainWithContracts)).not.toThrow();
});
});
});

describe('authorizers', () => {
it('allows simultaneous authorizers', () => {
const config = JSON.parse(
Expand Down Expand Up @@ -704,3 +773,87 @@ describe('authorizers', () => {
expect(() => chainConfigSchema.parse(validAuthorizersChainConfig)).not.toThrow();
});
});

describe('ensureCrossChainRequesterAuthorizerWithErc721', () => {
const config = JSON.parse(
readFileSync(join(__dirname, '../../test/fixtures/interpolated-config.valid.json')).toString()
);
const crossChainRequesterAuthorizerWithErc721 =
config.chains[0].authorizers.crossChainRequesterAuthorizersWithErc721[0];

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { contracts, ...crossChainWithoutAddress } = crossChainRequesterAuthorizerWithErc721;

it('adds the default RequesterAuthorizerWithErc721 contract address for the given chain if the chain has a deployment', () => {
const idWithDeployment = '1';
const crossChainWithDeployment = {
...crossChainWithoutAddress,
chainId: idWithDeployment,
};
const parsed = crossChainRequesterAuthorizersWithErc721Schema.parse(crossChainWithDeployment);
expect(parsed.contracts).toEqual({
RequesterAuthorizerWithErc721: RequesterAuthorizerWithErc721Addresses[idWithDeployment],
});
});

it('fails if RequesterAuthorizerWithErc721 contract address is not specified and there is no deployment for the chain', () => {
const idWithoutDeployment = '99999999999999999999999';
const crossChainWithoutDeployment = {
...crossChainWithoutAddress,
chainId: idWithoutDeployment,
};

expect(() => crossChainRequesterAuthorizersWithErc721Schema.parse(crossChainWithoutDeployment)).toThrow(
new ZodError([
{
code: 'custom',
message:
`RequesterAuthorizerWithErc721 contract address must be specified for chain ID '${idWithoutDeployment}'` +
`as there was no deployment for this chain exported from @api3/airnode-protocol`,
path: ['contracts'],
},
])
);
});
});

describe('ensureRequesterAuthorizerWithErc721', () => {
const config = JSON.parse(
readFileSync(join(__dirname, '../../test/fixtures/interpolated-config.valid.json')).toString()
);
const chainWithoutRequesterAuthorizerWithErc721Address = config.chains[0];
delete chainWithoutRequesterAuthorizerWithErc721Address.authorizers.requesterAuthorizersWithErc721[0]
.RequesterAuthorizerWithErc721;

it('adds the default RequesterAuthorizerWithErc721 contract address for the given chain if the chain has a deployment', () => {
const idWithDeployment = '1';
const configWithDeployment = {
...chainWithoutRequesterAuthorizerWithErc721Address,
id: idWithDeployment,
};
const parsed = chainConfigSchema.parse(configWithDeployment);
expect(parsed.authorizers.requesterAuthorizersWithErc721[0].RequesterAuthorizerWithErc721).toEqual(
RequesterAuthorizerWithErc721Addresses[idWithDeployment]
);
});

it('fails if RequesterAuthorizerWithErc721 contract address is not specified and there is no deployment for the chain', () => {
const idWithoutDeployment = '99999999999999999999999';
const crossChainWithoutDeployment = {
...chainWithoutRequesterAuthorizerWithErc721Address,
id: idWithoutDeployment,
};

expect(() => chainConfigSchema.parse(crossChainWithoutDeployment)).toThrow(
new ZodError([
{
code: 'custom',
message:
`RequesterAuthorizerWithErc721 contract address must be specified for chain ID '${idWithoutDeployment}'` +
`as there was no deployment for this chain exported from @api3/airnode-protocol`,
path: ['authorizers', 'requesterAuthorizersWithErc721', 0],
},
])
);
});
});
Loading

0 comments on commit deb1b35

Please sign in to comment.