mango-v4/ts/client/scripts/idl-compare.ts

196 lines
5.0 KiB
TypeScript

import { Idl } from '@coral-xyz/anchor';
import {
IdlEnumVariant,
IdlField,
IdlType,
IdlTypeDef,
} from '@coral-xyz/anchor/dist/cjs/idl';
import fs from 'fs';
function main(): void {
let hasError = false;
const oldIdl = JSON.parse(fs.readFileSync(process.argv[2], 'utf-8')) as Idl;
const newIdl = JSON.parse(fs.readFileSync(process.argv[3], 'utf-8')) as Idl;
// Old instructions still exist
for (const oldIx of oldIdl.instructions) {
if (!newIdl.instructions.find((x) => x.name == oldIx.name)) {
console.log(`Error: instruction '${oldIx.name}' was removed`);
hasError = true;
}
}
for (const oldAcc of oldIdl.accounts ?? []) {
const newAcc = newIdl.accounts?.find((x) => x.name == oldAcc.name);
// Old accounts still exist
if (!newAcc) {
console.log(`Error: account '${oldAcc.name}' was removed`);
hasError = true;
continue;
}
const oldSize = accountSize(oldIdl, oldAcc);
const newSize = accountSize(newIdl, newAcc);
if (oldSize != newSize) {
console.log(`Error: account '${oldAcc.name}' has changed size`);
hasError = true;
}
// Data offset matches for each account field with the same name
const newFields = newAcc.type.fields;
const oldFields = oldAcc.type.fields;
for (const oldField of oldFields) {
const newField = newFields.find((x) => x.name == oldField.name);
if (
oldField.name.startsWith('reserved') ||
oldField.name.startsWith('padding')
) {
continue;
}
// Old fields may be renamed / deprecated
if (!newField) {
console.log(
`Warning: account field '${oldAcc.name}.${oldField.name}' was removed`,
);
continue;
}
// Fields may not change size
const oldSize = typeSize(oldIdl, oldField.type);
const newSize = typeSize(newIdl, newField.type);
if (oldSize != newSize) {
console.log(
`Error: account field '${oldAcc.name}.${oldField.name}' has changed size`,
);
hasError = true;
}
// Fields may not change offset
const oldOffset = fieldOffset(oldFields, oldField, oldIdl);
const newOffset = fieldOffset(newFields, newField, newIdl);
if (oldOffset != newOffset) {
console.log(
`Error: account field '${oldAcc.name}.${oldField.name}' has changed offset`,
);
hasError = true;
}
}
}
process.exit(hasError ? 1 : 0);
}
main();
function fieldOffset(fields: IdlField[], field: IdlField, idl: Idl): number {
let offset = 0;
for (const f of fields) {
if (f.name == field.name) {
break;
}
offset += typeSize(idl, f.type);
}
return offset;
}
//
// The following code is essentially copied from anchor's common.ts
//
export function accountSize(idl: Idl, idlAccount: IdlTypeDef): number {
if (idlAccount.type.kind === 'enum') {
const variantSizes = idlAccount.type.variants.map(
(variant: IdlEnumVariant) => {
if (variant.fields === undefined) {
return 0;
}
return variant.fields
.map((f: IdlField | IdlType) => {
if (!(typeof f === 'object' && 'name' in f)) {
throw new Error('Tuple enum variants not yet implemented.');
}
return typeSize(idl, f.type);
})
.reduce((a: number, b: number) => a + b);
},
);
return Math.max(...variantSizes) + 1;
}
if (idlAccount.type.fields === undefined) {
return 0;
}
return idlAccount.type.fields
.map((f) => typeSize(idl, f.type))
.reduce((a, b) => a + b, 0);
}
function typeSize(idl: Idl, ty: IdlType): number {
switch (ty) {
case 'bool':
return 1;
case 'u8':
return 1;
case 'i8':
return 1;
case 'i16':
return 2;
case 'u16':
return 2;
case 'u32':
return 4;
case 'i32':
return 4;
case 'f32':
return 4;
case 'u64':
return 8;
case 'i64':
return 8;
case 'f64':
return 8;
case 'u128':
return 16;
case 'i128':
return 16;
case 'u256':
return 32;
case 'i256':
return 32;
case 'bytes':
return 1;
case 'string':
return 1;
case 'publicKey':
return 32;
default:
if ('vec' in ty) {
return 1;
}
if ('option' in ty) {
return 1 + typeSize(idl, ty.option);
}
if ('coption' in ty) {
return 4 + typeSize(idl, ty.coption);
}
if ('defined' in ty) {
const filtered = idl.types?.filter((t) => t.name === ty.defined) ?? [];
if (filtered.length !== 1) {
throw new Error(`Type not found: ${JSON.stringify(ty)}`);
}
const typeDef = filtered[0];
return accountSize(idl, typeDef);
}
if ('array' in ty) {
const arrayTy = ty.array[0];
const arraySize = ty.array[1];
return typeSize(idl, arrayTy) * arraySize;
}
throw new Error(`Invalid type ${JSON.stringify(ty)}`);
}
}