diff --git a/packages/abi/src/matchers/sway-type-matchers.test.ts b/packages/abi/src/matchers/sway-type-matchers.test.ts new file mode 100644 index 00000000000..314d01e70af --- /dev/null +++ b/packages/abi/src/matchers/sway-type-matchers.test.ts @@ -0,0 +1,247 @@ +import type { SwayType, swayTypeMatchers } from './sway-type-matchers'; +import { createMatcher } from './sway-type-matchers'; + +const testMappings: Record = { + string: 'string-matched', + void: 'void-matched', + bool: 'bool-matched', + u8: 'u8-matched', + u16: 'u16-matched', + u32: 'u32-matched', + u64: 'u64-matched', + u256: 'u256-matched', + b256: 'b256-matched', + generic: 'generic-matched', + stdString: 'stdString-matched', + option: 'option-matched', + result: 'result-matched', + enum: 'enum-matched', + struct: 'struct-matched', + b512: 'b512-matched', + bytes: 'bytes-matched', + vector: 'vector-matched', + tuple: 'tuple-matched', + array: 'array-matched', + assetId: 'assetId-matched', + evmAddress: 'evmAddress-matched', + rawUntypedPtr: 'rawUntypedPtr-matched', + rawUntypedSlice: 'rawUntypedSlice-matched', +}; + +const matcher = createMatcher(testMappings); + +function verifyOtherMatchersDontMatch(key: keyof typeof testMappings, swayType: string) { + const testMappingsWithoutKey = Object.fromEntries( + Object.entries(testMappings).filter(([k]) => k !== key) + ); + + const verifier = createMatcher(testMappingsWithoutKey as Record); + + expect(() => verifier({ swayType })).toThrow(`Matcher not found for sway type ${swayType}.`); +} + +/** + * @group node + * @group browser + */ +describe('sway type matchers', () => { + test('void', () => { + const key = 'void'; + const swayType = '()'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('bool', () => { + const key = 'bool'; + const swayType = 'bool'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('u8', () => { + const key = 'u8'; + const swayType = 'u8'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('u16', () => { + const key = 'u16'; + const swayType = 'u16'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('u32', () => { + const key = 'u32'; + const swayType = 'u32'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('u64', () => { + const key = 'u64'; + const swayType = 'u64'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('u256', () => { + const key = 'u256'; + const swayType = 'u256'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('b256', () => { + const key = 'b256'; + const swayType = 'b256'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('string', () => { + const key = 'string'; + const swayType = 'str[5]'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('array', () => { + const key = 'array'; + const swayType = '[_; 3]'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('tuple', () => { + const key = 'tuple'; + const swayType = '(_, _, _)'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('struct', () => { + const key = 'struct'; + const swayType = 'struct MyStruct'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('assetId', () => { + const key = 'assetId'; + const swayType = 'struct std::asset_id::AssetId'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('b512', () => { + const key = 'b512'; + const swayType = 'struct std::b512::B512'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('assetId', () => { + const key = 'assetId'; + const swayType = 'struct std::asset_id::AssetId'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('bytes', () => { + const key = 'bytes'; + const swayType = 'struct std::bytes::Bytes'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('stdString', () => { + const key = 'stdString'; + const swayType = 'struct std::string::String'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('evmAddress', () => { + const key = 'evmAddress'; + const swayType = 'struct std::vm::evm::evm_address::EvmAddress'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('vector', () => { + const key = 'vector'; + const swayType = 'struct std::vec::Vec'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('enum', () => { + const key = 'enum'; + const swayType = 'enum MyEnum'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('option', () => { + const key = 'option'; + const swayType = 'enum std::option::Option'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('result', () => { + const key = 'result'; + const swayType = 'enum std::result::Result'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('rawUntypedPtr', () => { + const key = 'rawUntypedPtr'; + const swayType = 'raw untyped ptr'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('rawUntypedSlice', () => { + const key = 'rawUntypedSlice'; + const swayType = 'raw untyped slice'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); + + test('generic', () => { + const key = 'generic'; + const swayType = 'generic T'; + + expect(matcher({ swayType })).toEqual(`${key}-matched`); + verifyOtherMatchersDontMatch(key, swayType); + }); +}); diff --git a/packages/abi/src/matchers/sway-type-matchers.ts b/packages/abi/src/matchers/sway-type-matchers.ts new file mode 100644 index 00000000000..22d68d6804a --- /dev/null +++ b/packages/abi/src/matchers/sway-type-matchers.ts @@ -0,0 +1,117 @@ +export type SwayType = + | 'void' + | 'bool' + | 'u8' + | 'u16' + | 'u32' + | 'u64' + | 'u256' + | 'b256' + | 'generic' + | 'string' + | 'stdString' + | 'option' + | 'result' + | 'enum' + | 'struct' + | 'b512' + | 'bytes' + | 'vector' + | 'tuple' + | 'array' + | 'assetId' + | 'evmAddress' + | 'rawUntypedPtr' // might not need it + | 'rawUntypedSlice'; // might not need it + +type Matcher = (type: string) => boolean; + +const voidMatcher: Matcher = (type) => type === '()'; +const bool: Matcher = (type) => type === 'bool'; +const u8: Matcher = (type) => type === 'u8'; +const u16: Matcher = (type) => type === 'u16'; +const u32: Matcher = (type) => type === 'u32'; +const u64: Matcher = (type) => type === 'u64'; +const u256: Matcher = (type) => type === 'u256'; +const b256: Matcher = (type) => type === 'b256'; + +export const GENERIC_REGEX = /^generic ([^\s]+)$/m; +const generic: Matcher = (type) => GENERIC_REGEX.test(type); + +export const STRING_REGEX = /str\[(?[0-9]+)\]/; +const string: Matcher = (type) => STRING_REGEX.test(type); + +export const TUPLE_REGEX = /^\((?.+)\)$/m; +const tuple: Matcher = (type) => TUPLE_REGEX.test(type); + +export const ARRAY_REGEX = /\[(?[\w\s\\[\]]+);\s*(?[0-9]+)\]/; +const array: Matcher = (type) => ARRAY_REGEX.test(type); + +const STRUCT_REGEX = /^struct .+$/m; +const STRUCT_STD_REGEX = + /^struct std::.*(AssetId|B512|Vec|RawVec|EvmAddress|Bytes|String|RawBytes)$/m; +const struct: Matcher = (type) => STRUCT_REGEX.test(type) && !STRUCT_STD_REGEX.test(type); +const assetId: Matcher = (type) => type === 'struct std::asset_id::AssetId'; +const b512: Matcher = (type) => type === 'struct std::b512::B512'; +const bytes: Matcher = (type) => type === 'struct std::bytes::Bytes'; +const evmAddress: Matcher = (type) => type === 'struct std::vm::evm::evm_address::EvmAddress'; +const stdString: Matcher = (type) => type === 'struct std::string::String'; +const vector: Matcher = (type) => type === 'struct std::vec::Vec'; + +const option: Matcher = (type) => type === 'enum std::option::Option'; +const result: Matcher = (type) => type === 'enum std::result::Result'; +const enumMatcher: Matcher = (type) => !option(type) && !result(type) && /^enum .*$/m.test(type); + +const rawUntypedPtr: Matcher = (type) => type === 'raw untyped ptr'; +const rawUntypedSlice: Matcher = (type) => type === 'raw untyped slice'; + +export const swayTypeMatchers: Record = { + void: voidMatcher, + generic, + bool, + u8, + u16, + u32, + u64, + u256, + b256, + + string, + tuple, + array, + + struct, + assetId, + b512, + bytes, + evmAddress, + stdString, + vector, + + enum: enumMatcher, + option, + result, + + rawUntypedPtr, + rawUntypedSlice, +}; + +const swayTypeMatcherEntries = Object.entries(swayTypeMatchers); + +export function createMatcher(mappings: Record) { + return (opts: { swayType: string }): T => { + const { swayType } = opts; + + for (const [key, matcher] of swayTypeMatcherEntries) { + if (matcher(swayType)) { + const mapping = mappings[key as SwayType]; + if (mapping) { + return mapping; + } + break; + } + } + + throw new Error(`Matcher not found for sway type ${swayType}.`); + }; +}