Skip to content

Commit

Permalink
Reapply "Reapply ABI generation outside of /dist (safe-global#1157)" (
Browse files Browse the repository at this point in the history
safe-global#1158) (safe-global#1159)

This reverts commit 73f5941.

This generates type safe ABIs are generated from the `@safe-global/safe-deployment` package.
  • Loading branch information
iamacook authored Feb 19, 2024
1 parent 73f5941 commit 79938db
Show file tree
Hide file tree
Showing 15 changed files with 127 additions and 121 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ lerna-debug.log*

# Database mounted volume
data

# ABIs
/abis
3 changes: 3 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
/.yarn

# ABIs
/abis
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ WORKDIR /app
COPY --chown=node:node .yarn/releases ./.yarn/releases
COPY --chown=node:node .yarn/patches ./.yarn/patches
COPY --chown=node:node package.json yarn.lock .yarnrc.yml tsconfig*.json ./
COPY --chown=node:node scripts/generate-abis.js ./scripts/generate-abis.js
RUN --mount=type=cache,target=/root/.yarn yarn
COPY --chown=node:node assets ./assets
COPY --chown=node:node migrations ./migrations
Expand All @@ -28,8 +29,9 @@ ARG BUILD_NUMBER
ENV APPLICATION_VERSION=${VERSION} \
APPLICATION_BUILD_NUMBER=${BUILD_NUMBER}

COPY --chown=node:node --from=base /app/abis ./abis
COPY --chown=node:node --from=base /app/node_modules ./node_modules
COPY --chown=node:node --from=base /app/dist ./dist
COPY --chown=node:node --from=base /app/assets ./assets
COPY --chown=node:node --from=base /app/migrations ./migrations
CMD [ "node", "dist/main.js" ]
CMD [ "node", "dist/src/main.js" ]
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
"private": true,
"license": "MIT",
"scripts": {
"build": "nest build",
"build": "yarn generate-abis && nest build",
"format": "prettier --write .",
"format-check": "prettier --check .",
"generate-abis": "node ./scripts/generate-abis.js",
"postinstall": "yarn generate-abis",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"start:prod": "node dist/src/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"lint-check": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"test": "jest",
Expand Down Expand Up @@ -91,6 +93,7 @@
],
"testEnvironment": "node",
"moduleNameMapper": {
"^@/abis/(.*)$": "<rootDir>/../abis/$1",
"^@/(.*)$": "<rootDir>/../src/$1"
},
"globalSetup": "<rootDir>/../test/global-setup.ts"
Expand Down
83 changes: 83 additions & 0 deletions scripts/generate-abis.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const path = require('path');
const fs = require('fs');

/**
* This generates const TypeScript ABIs for each asset in
* `@safe-global/safe-deployments` package for `viem` to infer.
*
* Although it is possible to get a singleton programmatically and
* import the JSON directly, neither is strictly typed.
*
* Once it is possible to import JSON "as const", the deployments
* package should be updated to return the singletons as such.
*
* @see https://github.com/microsoft/TypeScript/issues/32063
*/

// Path to directory containing JSON assets
const assetsDir = path.join(
process.cwd(),
'node_modules',
'@safe-global',
'safe-deployments',
'dist',
'assets',
);

// Path to directory where ABIs will be written
const outputDir = path.join(process.cwd(), 'abis', 'safe');

// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
function main() {
// Remove any existing ABIs
try {
fs.rmSync(outputDir, { recursive: true });
} catch {
// Swallow error if directory does not exist (first run)
}

// For each version...
for (const version of fs.readdirSync(assetsDir)) {
const versionOutputDir = path.join(outputDir, version);

fs.mkdirSync(versionOutputDir, { recursive: true });

const versionDir = path.join(assetsDir, version);

// ...parse the ABI for each asset
for (const assetFile of fs.readdirSync(versionDir)) {
// Read the asset JSON
const assetPath = path.join(assetsDir, version, assetFile);
const assetJson = fs.readFileSync(assetPath, 'utf8');

// Parse the asset JSON
const { contractName, abi } = JSON.parse(assetJson);

// Write the ABI to a file
const fileName = `${contractName}.abi.ts`;
const filePath = path.join(versionOutputDir, fileName);

// It is generally better to use the Stream API for larger files
// but as we are storing the JSON in memory, this is likely of
// minimal benefit. As this script runs on build, we need not
// worry too much about performance though.
const stream = fs.createWriteStream(filePath);

// Write formatted ABI to file
stream.write(
'// This file is auto-generated by scripts/generate-abis.js\nexport default ',
);
stream.write(JSON.stringify(abi, null, 2));

// Most important step: assert ABI as readonly
stream.write(' as const;');

stream.end();
}
}

console.log('ABIs generated successfully!');
}

main();
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import {
encodePacked,
getAddress,
Hex,
parseAbi,
size,
} from 'viem';
import MultiSendCallOnly130 from '@/abis/safe/v1.3.0/MultiSendCallOnly.abi';
import { Builder } from '@/__tests__/builder';

// multiSend
Expand All @@ -21,16 +21,11 @@ class MultiSendEncoder<T extends MultiSendArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE =
'function multiSend(bytes memory transactions)' as const;

encode(): Hex {
const abi = parseAbi([MultiSendEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: MultiSendCallOnly130,
functionName: 'multiSend',
args: [args.transactions],
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { Hex } from 'viem';
import { faker } from '@faker-js/faker';
import { SafeDecoder } from '@/domain/contracts/contracts/safe-decoder.helper';
import {
addOwnerWithThresholdEncoder,
Expand Down Expand Up @@ -103,10 +101,4 @@ describe('SafeDecoder', () => {
],
});
});

it('throws if the function call cannot be decoded', () => {
const data = faker.string.hexadecimal({ length: 138 }) as Hex;

expect(() => target.decodeFunctionData({ data })).toThrow();
});
});
72 changes: 12 additions & 60 deletions src/domain/contracts/contracts/__tests__/safe-encoder.builder.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { faker } from '@faker-js/faker';
import { encodeFunctionData, getAddress, Hex, pad, parseAbi } from 'viem';

import { encodeFunctionData, getAddress, Hex, pad } from 'viem';
import Safe130 from '@/abis/safe/v1.3.0/GnosisSafe.abi';
import { Safe } from '@/domain/safe/entities/safe.entity';
import { IEncoder } from '@/__tests__/encoder-builder';
import { Builder } from '@/__tests__/builder';
Expand Down Expand Up @@ -38,16 +38,11 @@ type SetupArgs = {
};

class SetupEncoder<T extends SetupArgs> extends Builder<T> implements IEncoder {
static readonly FUNCTION_SIGNATURE =
'function setup(address[] calldata _owners, uint256 _threshold, address to, bytes calldata data, address fallbackHandler, address paymentToken, uint256 payment, address paymentReceiver)';

encode(): Hex {
const abi = parseAbi([SetupEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'setup',
args: [
args.owners,
Expand Down Expand Up @@ -94,16 +89,11 @@ class ExecTransactionEncoder<T extends ExecTransactionArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE =
'function execTransaction(address to, uint256 value, bytes calldata data, uint8 operation, uint256 safeTxGas, uint256 baseGas, uint256 gasPrice, address gasToken, address refundReceiver, bytes signatures)' as const;

encode(): Hex {
const abi = parseAbi([ExecTransactionEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'execTransaction',
args: [
args.to,
Expand Down Expand Up @@ -146,16 +136,11 @@ class AddOwnerWithThresholdEncoder<T extends AddOwnerWithThresholdArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE =
'function addOwnerWithThreshold(address owner, uint256 _threshold)' as const;

encode(): Hex {
const abi = parseAbi([AddOwnerWithThresholdEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'addOwnerWithThreshold',
args: [args.owner, args.threshold],
});
Expand All @@ -180,16 +165,11 @@ class RemoveOwnerEncoder<T extends RemoveOwnerArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE =
'function removeOwner(address prevOwner, address owner, uint256 _threshold)';

encode(): Hex {
const abi = parseAbi([RemoveOwnerEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'removeOwner',
args: [args.prevOwner, args.owner, args.threshold],
});
Expand Down Expand Up @@ -220,16 +200,11 @@ class SwapOwnerEncoder<T extends SwapOwnerArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE =
'function swapOwner(address prevOwner, address oldOwner, address newOwner)';

encode(): Hex {
const abi = parseAbi([SwapOwnerEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'swapOwner',
args: [args.prevOwner, args.oldOwner, args.newOwner],
});
Expand Down Expand Up @@ -258,16 +233,11 @@ class ChangeThresholdEncoder<T extends ChangeThresholdArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE =
'function changeThreshold(uint256 _threshold)';

encode(): Hex {
const abi = parseAbi([ChangeThresholdEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'changeThreshold',
args: [args.threshold],
});
Expand All @@ -291,15 +261,11 @@ class EnableModuleEncoder<T extends EnableModuleArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE = 'function enableModule(address module)';

encode(): Hex {
const abi = parseAbi([EnableModuleEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'enableModule',
args: [args.module],
});
Expand All @@ -324,16 +290,11 @@ class DisableModuleEncoder<T extends DisableModuleArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE =
'function disableModule(address prevModule, address module)';

encode(): Hex {
const abi = parseAbi([DisableModuleEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'disableModule',
args: [args.prevModule, args.module],
});
Expand All @@ -356,16 +317,11 @@ class SetFallbackHandlerEncoder<T extends SetFallbackHandlerArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE =
'function setFallbackHandler(address handler)';

encode(): Hex {
const abi = parseAbi([SetFallbackHandlerEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'setFallbackHandler',
args: [args.handler],
});
Expand All @@ -389,15 +345,11 @@ class SetGuardEncoder<T extends SetGuardArgs>
extends Builder<T>
implements IEncoder
{
static readonly FUNCTION_SIGNATURE = 'function setGuard(address guard)';

encode(): Hex {
const abi = parseAbi([SetGuardEncoder.FUNCTION_SIGNATURE]);

const args = this.build();

return encodeFunctionData({
abi,
abi: Safe130,
functionName: 'setGuard',
args: [args.guard],
});
Expand Down
Loading

0 comments on commit 79938db

Please sign in to comment.