Skip to content

Commit 7c5a735

Browse files
authored
Merge branch 'main' into dependabot/github_actions/minor-and-patch-00a0d7aaea
2 parents 1740615 + c097ac7 commit 7c5a735

14 files changed

Lines changed: 241 additions & 22 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@aragon/gov-ui-kit": minor
3+
---
4+
5+
Enforce EVM-stringent validation on `ProposalActionsDecoder` inputs: validate signed `int*` values (previously unvalidated), check that numeric values fit the bit-width of their type (e.g. `uint8` must be 0-255), verify EIP-55 address checksums (all-lowercase addresses are still accepted), and strip negative signs from unsigned number inputs. Adds `signedNumber` and `numberRange` messages to the `proposalActionsDecoder.validation` copy.

.changeset/giant-oranges-like.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@aragon/gov-ui-kit": patch
3+
---
4+
5+
Fix SVG build config with missing `prefixIds` plugin

.storybook/main.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { RollupOptions } from 'rollup';
55
import { mergeConfig } from 'vite';
66
import { viteStaticCopy } from 'vite-plugin-static-copy';
77
import svgr from 'vite-plugin-svgr';
8+
import svgoConfig from '../svgo.config.js';
89

910
const config: StorybookConfig = {
1011
stories: ['../docs/**/*.@(md|mdx)', '../src/**/*.stories.@(js|jsx|ts|tsx)', '../src/**/*.@(md|mdx)'],
@@ -24,7 +25,12 @@ const config: StorybookConfig = {
2425
viteFinal: (viteConfig) => {
2526
// Add source-map-js alias and plugins for importing svg files and copying fonts
2627
const plugins = [
27-
svgr({ include: '**/*.svg' }),
28+
// Run the same SVGO pass (including id namespacing, see svgo.config.js) as the
29+
// published library build, so Storybook previews match production markup.
30+
svgr({
31+
include: '**/*.svg',
32+
svgrOptions: { plugins: ['@svgr/plugin-svgo', '@svgr/plugin-jsx'], svgoConfig },
33+
}),
2834
viteStaticCopy({ targets: [{ src: './src/theme/fonts/*.ttf', dest: './fonts' }] }),
2935
];
3036
const resolve = { alias: { 'source-map-js': 'source-map' } };

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@
104104
"@rollup/plugin-typescript": "^12.3.0",
105105
"@storybook/addon-docs": "^10.4.6",
106106
"@storybook/react-vite": "^10.4.6",
107+
"@svgr/plugin-jsx": "^8.1.0",
108+
"@svgr/plugin-svgo": "^8.1.0",
107109
"@svgr/rollup": "^8.1.0",
108110
"@tailwindcss/postcss": "^4.3.1",
109111
"@tailwindcss/typography": "^0.5.20",
@@ -133,6 +135,7 @@
133135
"rollup-plugin-postcss": "^4.0.2",
134136
"rollup-plugin-visualizer": "^7.0.1",
135137
"storybook": "^10.4.6",
138+
"svgo": "^3.3.3",
136139
"tailwindcss": "^4.3.1",
137140
"ts-jest": "^29.4.11",
138141
"tslib": "^2.8.1",

pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rollup.config.mjs

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import svgr from '@svgr/rollup';
88
import peerDepsExternal from 'rollup-plugin-peer-deps-external';
99
import postcss from 'rollup-plugin-postcss';
1010
import { visualizer } from 'rollup-plugin-visualizer';
11+
import svgoConfig from './svgo.config.js';
1112

1213
const require = createRequire(import.meta.url);
1314
const tsConfig = require('./tsconfig.json');
@@ -52,19 +53,7 @@ export default [
5253
],
5354
}),
5455
images({ include: ['**/*.png', '**/*.jpg'] }),
55-
svgr({
56-
// Keep the viewBox so icons scale instead of getting clipped when
57-
// rendered at a size other than their intrinsic 16px (e.g. size-3 in
58-
// small buttons). SVGO's preset-default removes it by default.
59-
svgoConfig: {
60-
plugins: [
61-
{
62-
name: 'preset-default',
63-
params: { overrides: { removeViewBox: false } },
64-
},
65-
],
66-
},
67-
}),
56+
svgr({ svgoConfig }),
6857
terser(),
6958
],
7059
},

src/modules/assets/copy/modulesCopy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ export const modulesCopy = {
6969
address: (label: string) => `${label} is not a valid address.`,
7070
bytes: (label: string) => `${label} is not a valid bytes value.`,
7171
unsignedNumber: (label: string) => `${label} is not a valid uint value.`,
72+
signedNumber: (label: string) => `${label} is not a valid int value.`,
73+
numberRange: (label: string, type: string) => `${label} is out of range for ${type}.`,
7274
},
7375
},
7476
proposalActionChangeMembers: {

src/modules/components/proposal/proposalActions/proposalActionsDecoder/proposalActionsDecoderBooleanField/proposalActionsDecoderBooleanField.test.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,26 @@ describe('<ProposalActionsDecoderBooleanField /> component', () => {
6161
expect(useControllerSpy).toHaveBeenCalledWith(expect.objectContaining({ name: `${formPrefix}.${fieldName}` }));
6262
});
6363

64+
it('normalises string field values to booleans to support transaction data encoding', () => {
65+
const onChange = jest.fn();
66+
useControllerSpy.mockReturnValue({
67+
fieldState: { error: undefined },
68+
field: { value: 'true', onBlur: jest.fn(), onChange, ref: jest.fn() },
69+
} as unknown as ReactHookForm.UseControllerReturn);
70+
render(createTestComponent());
71+
expect(onChange).toHaveBeenCalledWith(true);
72+
});
73+
74+
it('does not update the field value when it is already a boolean', () => {
75+
const onChange = jest.fn();
76+
useControllerSpy.mockReturnValue({
77+
fieldState: { error: undefined },
78+
field: { value: false, onBlur: jest.fn(), onChange, ref: jest.fn() },
79+
} as unknown as ReactHookForm.UseControllerReturn);
80+
render(createTestComponent());
81+
expect(onChange).not.toHaveBeenCalled();
82+
});
83+
6484
it('renders the parameter name, type and notice as field labels', () => {
6585
const parameter = { name: 'tryExecute', notice: 'description', type: 'bool', value: undefined };
6686
render(createTestComponent({ parameter }));

src/modules/components/proposal/proposalActions/proposalActionsDecoder/proposalActionsDecoderBooleanField/proposalActionsDecoderBooleanField.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect } from 'react';
12
import { useController } from 'react-hook-form';
23
import { Radio, RadioGroup } from '../../../../../../core';
34
import { useGukModulesContext } from '../../../../gukModulesProvider';
@@ -39,13 +40,23 @@ export const ProposalActionsDecoderBooleanField: React.FC<IProposalActionsDecode
3940
const { error } = fieldState;
4041
const alert = error?.message == null ? undefined : { message: error.message, variant: 'critical' as const };
4142

43+
const { value, onChange } = field;
44+
45+
useEffect(() => {
46+
// Normalise string values ("true" / "false") seeded by the application to booleans as the transaction data
47+
// encoding only accepts boolean values for bool types.
48+
if (typeof value === 'string' && proposalActionsDecoderUtils.validateBoolean(value)) {
49+
onChange(value === 'true');
50+
}
51+
}, [value, onChange]);
52+
4253
const label = hideLabels ? undefined : (
4354
<>
4455
{name} <span className="text-neutral-500">({type})</span>
4556
</>
4657
);
4758

48-
const handleValueChange = (value: string) => field.onChange(value === 'true');
59+
const handleValueChange = (newValue: string) => onChange(newValue === 'true');
4960

5061
return (
5162
<RadioGroup
@@ -56,7 +67,7 @@ export const ProposalActionsDecoderBooleanField: React.FC<IProposalActionsDecode
5667
onBlur={field.onBlur}
5768
onValueChange={handleValueChange}
5869
ref={field.ref}
59-
value={field.value?.toString() ?? ''}
70+
value={value?.toString() ?? ''}
6071
>
6172
<Radio label={copy.proposalActionsDecoder.booleanTrue} value="true" />
6273
<Radio label={copy.proposalActionsDecoder.booleanFalse} value="false" />

src/modules/components/proposal/proposalActions/proposalActionsDecoder/proposalActionsDecoderTextField/proposalActionsDecoderTextFieldEdit.test.tsx

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ describe('<ProposalActionsDecoderTextFieldEdit /> component', () => {
130130
expect(onChange).toHaveBeenCalledWith('tru');
131131
});
132132

133-
it('triggers the onChange callback removing the non-numberic values when type is a number', async () => {
133+
it('triggers the onChange callback removing the non-numeric values and negative signs when type is an unsigned number', async () => {
134134
const onChange = jest.fn();
135135
const parameter = { name: 'uintParam', type: 'uint32', value: undefined };
136136
const controllerReturn = {
@@ -143,6 +143,22 @@ describe('<ProposalActionsDecoderTextFieldEdit /> component', () => {
143143
} as useFormContext.UseFormContextReturn);
144144
render(createTestComponent({ parameter }));
145145
await userEvent.type(screen.getByRole('textbox'), '1');
146+
expect(onChange).toHaveBeenCalledWith('321');
147+
});
148+
149+
it('triggers the onChange callback keeping one leading negative sign when type is a signed number', async () => {
150+
const onChange = jest.fn();
151+
const parameter = { name: 'intParam', type: 'int32', value: undefined };
152+
const controllerReturn = {
153+
fieldState: { error: undefined },
154+
field: { value: 'ab--32.', onChange },
155+
} as unknown as ReactHookForm.UseControllerReturn;
156+
useControllerSpy.mockReturnValue(controllerReturn);
157+
useFormContextSpy.mockReturnValue({
158+
watch: () => controllerReturn.field.value as unknown,
159+
} as useFormContext.UseFormContextReturn);
160+
render(createTestComponent({ parameter }));
161+
await userEvent.type(screen.getByRole('textbox'), '1');
146162
expect(onChange).toHaveBeenCalledWith('-321');
147163
});
148164

0 commit comments

Comments
 (0)