From 07586221e8653c7fdf7e83f56ac73476cf07e925 Mon Sep 17 00:00:00 2001 From: lbqds Date: Fri, 10 May 2024 07:56:04 +0800 Subject: [PATCH] Gen code for maps --- .project.json | 14 ++-- artifacts/test/MapTest.ral.json | 40 +---------- artifacts/ts/MapTest.ts | 97 +++----------------------- contracts/test/map.ral | 8 --- packages/cli/src/codegen.ts | 18 +++++ packages/web3/src/contract/contract.ts | 50 +++++++++++++ test/contract.test.ts | 27 +++++-- 7 files changed, 106 insertions(+), 148 deletions(-) diff --git a/.project.json b/.project.json index 06a2853e4..11ff43329 100644 --- a/.project.json +++ b/.project.json @@ -166,7 +166,7 @@ }, "InsertIntoMap": { "sourceFile": "test/map.ral", - "sourceCodeHash": "c486f9e27600a65c8b6461b39cecf71fc4bd466330568a66e2652693a91b612e", + "sourceCodeHash": "f2f308aaa25fb2bfd984ce7e8f38ba846908a8c0e1a7396244d40e734327b77f", "bytecodeDebugPatch": "", "codeHashDebug": "", "warnings": [] @@ -182,16 +182,16 @@ }, "MapTest": { "sourceFile": "test/map.ral", - "sourceCodeHash": "c486f9e27600a65c8b6461b39cecf71fc4bd466330568a66e2652693a91b612e", - "bytecodeDebugPatch": "=6-2+6e=2-1=1-4+e=2+5a4=1-1+82=2-2+9b=11-1+9=40+7a7e0214696e73657274206174206d617020706174683a2000=56+7a7e0214696e73657274206174206d617020706174683a2000=217-1+8=114+7a7e021472656d6f7665206174206d617020706174683a2000=46+7a7e021472656d6f7665206174206d617020706174683a2000=136", - "codeHashDebug": "0b93613225107052726d5a66e898b0734b982e0c841c3972cb6a682cc78aca00", + "sourceCodeHash": "f2f308aaa25fb2bfd984ce7e8f38ba846908a8c0e1a7396244d40e734327b77f", + "bytecodeDebugPatch": "=6-2+6e=2-1=1+e=1-3+15a=11-1+9=40+7a7e0214696e73657274206174206d617020706174683a2000=56+7a7e0214696e73657274206174206d617020706174683a2000=217-1+8=114+7a7e021472656d6f7665206174206d617020706174683a2000=46+7a7e021472656d6f7665206174206d617020706174683a2000=6", + "codeHashDebug": "c931059b5efe98209414ee5cc2c48061973ec4ff6bd59ac603721e680670d377", "warnings": [ "No external caller check for function \"MapTest.insert\". Please use \"checkCaller!(...)\" in the function or its callees, or disable it with \"@using(checkExternalCaller = false)\"." ] }, "MapValue": { "sourceFile": "test/map.ral", - "sourceCodeHash": "c486f9e27600a65c8b6461b39cecf71fc4bd466330568a66e2652693a91b612e", + "sourceCodeHash": "f2f308aaa25fb2bfd984ce7e8f38ba846908a8c0e1a7396244d40e734327b77f", "bytecodeDebugPatch": "", "codeHashDebug": "", "warnings": [] @@ -258,7 +258,7 @@ }, "RemoveFromMap": { "sourceFile": "test/map.ral", - "sourceCodeHash": "c486f9e27600a65c8b6461b39cecf71fc4bd466330568a66e2652693a91b612e", + "sourceCodeHash": "f2f308aaa25fb2bfd984ce7e8f38ba846908a8c0e1a7396244d40e734327b77f", "bytecodeDebugPatch": "", "codeHashDebug": "", "warnings": [] @@ -300,7 +300,7 @@ }, "UpdateMapValue": { "sourceFile": "test/map.ral", - "sourceCodeHash": "c486f9e27600a65c8b6461b39cecf71fc4bd466330568a66e2652693a91b612e", + "sourceCodeHash": "f2f308aaa25fb2bfd984ce7e8f38ba846908a8c0e1a7396244d40e734327b77f", "bytecodeDebugPatch": "", "codeHashDebug": "", "warnings": [] diff --git a/artifacts/test/MapTest.ral.json b/artifacts/test/MapTest.ral.json index 5e8ef4b63..b23c89469 100644 --- a/artifacts/test/MapTest.ral.json +++ b/artifacts/test/MapTest.ral.json @@ -1,8 +1,8 @@ { "version": "v2.11.0", "name": "MapTest", - "bytecode": "0005403c409c40f6411e41370103030300151600d1a2140a5f5f6d61705f5f305f5f160047441601b11602d202011600d1a2140a5f5f6d61705f5f315f5f16014044b11602d201010100010400402a0c0d0d140a5f5f6d61705f5f305f5f16004744cb1703160301000c0d0d160301011702170116020d2a0c0e0c140a5f5f6d61705f5f305f5f16004744cb010216020d2a0c0e0c140a5f5f6d61705f5f315f5f16014044cb0102010001040040240c0d0d140a5f5f6d61705f5f305f5f16004744cb1703160301000c0d0d160301011702170116000d0c140a5f5f6d61705f5f305f5f16004744cb010316000d0c140a5f5f6d61705f5f315f5f16014044cb01030100010202110c0d0d140a5f5f6d61705f5f305f5f16004744cb1701160101000c0d0d1601010102010001010107140a5f5f6d61705f5f305f5f16004744cbc502", - "codeHash": "9ff6f47837ee2d8f2cb22fe5cfeb05f64d42915aa8b30dbb96783f3e99c363f6", + "bytecode": "0003403c409c40f60103030300151600d1a2140a5f5f6d61705f5f305f5f160047441601b11602d202011600d1a2140a5f5f6d61705f5f315f5f16014044b11602d201010100010400402a0c0d0d140a5f5f6d61705f5f305f5f16004744cb1703160301000c0d0d160301011702170116020d2a0c0e0c140a5f5f6d61705f5f305f5f16004744cb010216020d2a0c0e0c140a5f5f6d61705f5f315f5f16014044cb0102010001040040240c0d0d140a5f5f6d61705f5f305f5f16004744cb1703160301000c0d0d160301011702170116000d0c140a5f5f6d61705f5f305f5f16004744cb010316000d0c140a5f5f6d61705f5f315f5f16014044cb0103", + "codeHash": "05b65f931ca86fb21d222e22007c606c9de7a1d3cb602dd56df21e7b431efac5", "fieldsSig": { "names": [], "types": [], @@ -60,42 +60,6 @@ false ], "returnTypes": [] - }, - { - "name": "get", - "usePreapprovedAssets": false, - "useAssetsInContract": false, - "isPublic": true, - "paramNames": [ - "key" - ], - "paramTypes": [ - "Address" - ], - "paramIsMutable": [ - false - ], - "returnTypes": [ - "MapValue" - ] - }, - { - "name": "contains", - "usePreapprovedAssets": false, - "useAssetsInContract": false, - "isPublic": true, - "paramNames": [ - "key" - ], - "paramTypes": [ - "Address" - ], - "paramIsMutable": [ - false - ], - "returnTypes": [ - "Bool" - ] } ], "constants": [], diff --git a/artifacts/ts/MapTest.ts b/artifacts/ts/MapTest.ts index e3243d858..833ad62cd 100644 --- a/artifacts/ts/MapTest.ts +++ b/artifacts/ts/MapTest.ts @@ -31,33 +31,11 @@ import { import { default as MapTestContractJson } from "../test/MapTest.ral.json"; import { getContractByCodeHash } from "./contracts"; import { Balances, MapValue, TokenBalance, AllStructs } from "./types"; +import { RalphMap } from "@alephium/web3"; // Custom types for the contract export namespace MapTestTypes { export type State = Omit, "fields">; - - export interface CallMethodTable { - get: { - params: CallContractParams<{ key: Address }>; - result: CallContractResult; - }; - contains: { - params: CallContractParams<{ key: Address }>; - result: CallContractResult; - }; - } - export type CallMethodParams = - CallMethodTable[T]["params"]; - export type CallMethodResult = - CallMethodTable[T]["result"]; - export type MultiCallParams = Partial<{ - [Name in keyof CallMethodTable]: CallMethodTable[Name]["params"]; - }>; - export type MultiCallResults = { - [MaybeName in keyof T]: MaybeName extends keyof CallMethodTable - ? CallMethodTable[MaybeName]["result"] - : undefined; - }; } class Factory extends ContractFactory { @@ -121,40 +99,6 @@ class Factory extends ContractFactory { > => { return testMethod(this, "remove", params); }, - get: async ( - params: Omit< - TestContractParams< - never, - { key: Address }, - { map0?: Map; map1?: Map } - >, - "initialFields" - > - ): Promise< - TestContractResult< - MapValue, - { map0?: Map; map1?: Map } - > - > => { - return testMethod(this, "get", params); - }, - contains: async ( - params: Omit< - TestContractParams< - never, - { key: Address }, - { map0?: Map; map1?: Map } - >, - "initialFields" - > - ): Promise< - TestContractResult< - boolean, - { map0?: Map; map1?: Map } - > - > => { - return testMethod(this, "contains", params); - }, }; } @@ -162,8 +106,8 @@ class Factory extends ContractFactory { export const MapTest = new Factory( Contract.fromJson( MapTestContractJson, - "=6-2+6e=2-1=1-4+e=2+5a4=1-1+82=2-2+9b=11-1+9=40+7a7e0214696e73657274206174206d617020706174683a2000=56+7a7e0214696e73657274206174206d617020706174683a2000=217-1+8=114+7a7e021472656d6f7665206174206d617020706174683a2000=46+7a7e021472656d6f7665206174206d617020706174683a2000=136", - "0b93613225107052726d5a66e898b0734b982e0c841c3972cb6a682cc78aca00", + "=6-2+6e=2-1=1+e=1-3+15a=11-1+9=40+7a7e0214696e73657274206174206d617020706174683a2000=56+7a7e0214696e73657274206174206d617020706174683a2000=217-1+8=114+7a7e021472656d6f7665206174206d617020706174683a2000=46+7a7e021472656d6f7665206174206d617020706174683a2000=6", + "c931059b5efe98209414ee5cc2c48061973ec4ff6bd59ac603721e680670d377", AllStructs ) ); @@ -174,37 +118,12 @@ export class MapTestInstance extends ContractInstance { super(address); } - async fetchState(): Promise { - return fetchContractState(MapTest, this); - } - - methods = { - get: async ( - params: MapTestTypes.CallMethodParams<"get"> - ): Promise> => { - return callMethod(MapTest, this, "get", params, getContractByCodeHash); - }, - contains: async ( - params: MapTestTypes.CallMethodParams<"contains"> - ): Promise> => { - return callMethod( - MapTest, - this, - "contains", - params, - getContractByCodeHash - ); - }, + maps = { + map0: new RalphMap(MapTest.contract, this, "map0"), + map1: new RalphMap(MapTest.contract, this, "map1"), }; - async multicall( - calls: Calls - ): Promise> { - return (await multicallMethods( - MapTest, - this, - calls, - getContractByCodeHash - )) as MapTestTypes.MultiCallResults; + async fetchState(): Promise { + return fetchContractState(MapTest, this); } } diff --git a/contracts/test/map.ral b/contracts/test/map.ral index 6ae85843d..e59a80db9 100644 --- a/contracts/test/map.ral +++ b/contracts/test/map.ral @@ -23,14 +23,6 @@ Contract MapTest() { map0.remove!(key, key) map1.remove!(key, value.id) } - - pub fn get(key: Address) -> MapValue { - return map0[key] - } - - pub fn contains(key: Address) -> Bool { - return map0.contains!(key) - } } TxScript InsertIntoMap(mapTest: MapTest, from: Address, value: MapValue) { diff --git a/packages/cli/src/codegen.ts b/packages/cli/src/codegen.ts index 34857bc66..92a3033ec 100644 --- a/packages/cli/src/codegen.ts +++ b/packages/cli/src/codegen.ts @@ -140,6 +140,22 @@ function contractFieldType(contractName: string, fieldsSig: node.FieldsSig): str return hasFields ? `${contractTypes(contractName)}.Fields` : '{}' } +function importRalphMap(contract: Contract): string { + if (contract.mapsSig === undefined) return '' + return `import { RalphMap } from '@alephium/web3'` +} + +function genMaps(contract: Contract): string { + const mapsSig = contract.mapsSig + if (mapsSig === undefined) return '' + const maps = mapsSig.names.map((name, index) => { + const mapType = mapsSig.types[`${index}`] + const [key, value] = parseMapType(mapType) + return `${name}: new RalphMap<${toTsType(key)}, ${toTsType(value)}>(${contract.name}.contract, this, '${name}')` + }) + return `maps = { ${maps.join(',')} }` +} + function genFetchState(contract: Contract): string { return ` async fetchState(): Promise<${contractTypes(contract.name)}.State> { @@ -440,6 +456,7 @@ function genContract(contract: Contract, artifactRelativePath: string): string { import { default as ${contract.name}ContractJson } from '../${toUnixPath(artifactRelativePath)}' import { getContractByCodeHash } from './contracts' ${importStructs()} + ${importRalphMap(contract)} // Custom types for the contract export namespace ${contract.name}Types { @@ -471,6 +488,7 @@ function genContract(contract: Contract, artifactRelativePath: string): string { super(address) } + ${genMaps(contract)} ${genFetchState(contract)} ${genGetContractEventsCurrentCount(contract)} ${contract.eventsSig.map((e) => genSubscribeEvent(contract.name, e)).join('\n')} diff --git a/packages/web3/src/contract/contract.ts b/packages/web3/src/contract/contract.ts index 67bec065f..0cb6b37b5 100644 --- a/packages/web3/src/contract/contract.ts +++ b/packages/web3/src/contract/contract.ts @@ -2007,6 +2007,56 @@ export async function testMethod< } as TestContractResult } +export class RalphMap { + constructor( + private readonly parentContract: Contract, + private readonly parentInstance: ContractInstance, + private readonly mapName: string + ) {} + + async get(key: K): Promise { + return getMapItem(this.parentContract, this.parentInstance, this.mapName, key) + } + + async contains(key: K): Promise { + return this.get(key).then((v) => v !== undefined) + } +} + +export async function getMapItem( + parentContract: Contract, + parentInstance: ContractInstance, + mapName: string, + key: Val +): Promise { + const index = parentContract.mapsSig?.names.findIndex((name) => name === mapName) + const mapType = index === undefined ? undefined : parentContract.mapsSig?.types[`${index}`] + if (mapType === undefined) { + throw new Error(`Map ${mapName} does not exist in contract ${parentContract.name}`) + } + const [keyType, valueType] = ralph.parseMapType(mapType) + const mapItemContractId = calcWrapperContractId( + parentInstance.contractId, + index!, + key, + keyType, + parentInstance.groupIndex + ) + const mapItemAddress = addressFromContractId(mapItemContractId) + try { + const state = await getCurrentNodeProvider().contracts.getContractsAddressState(mapItemAddress) + const fieldsSig = getContractFieldsSig(valueType) + const fields = fromApiFields(state.immFields, state.mutFields, fieldsSig, parentContract.structs) + return fields['value'] as R + } catch (error) { + if (error instanceof Error && error.message.includes('KeyNotFound')) { + // the map item contract does not exist + return undefined + } + throw error + } +} + interface MapInfo { name: string value: Map diff --git a/test/contract.test.ts b/test/contract.test.ts index 1b912d7f0..2b26096fd 100644 --- a/test/contract.test.ts +++ b/test/contract.test.ts @@ -50,6 +50,7 @@ import { RemoveFromMap, TemplateArrayVar, TestAssert, + UpdateMapValue, UpdateUserAccount } from '../artifacts/ts/scripts' import { Sub, SubTypes } from '../artifacts/ts/Sub' @@ -511,11 +512,23 @@ describe('contract', function () { attoAlphAmount: ONE_ALPH * 2n }) - const exist0 = (await mapTest.methods.contains({ args: { key: signer.address } })).returns - expect(exist0).toEqual(true) + const invalidAddress = randomContractAddress() + expect(await mapTest.maps.map0.contains(invalidAddress)).toEqual(false) + expect(await mapTest.maps.map0.contains(signer.address)).toEqual(true) + expect(await mapTest.maps.map0.get(signer.address)).toEqual({ id: 1n, balance: 10n }) + expect(await mapTest.maps.map1.contains(0n)).toEqual(false) + expect(await mapTest.maps.map1.contains(1n)).toEqual(true) + expect(await mapTest.maps.map1.get(1n)).toEqual(10n) - const value = (await mapTest.methods.get({ args: { key: signer.address } })).returns - expect(value).toEqual({ id: 1n, balance: 10n }) + await UpdateMapValue.execute(signer, { + initialFields: { + mapTest: mapTest.contractId, + key: signer.address + } + }) + + expect(await mapTest.maps.map0.get(signer.address)).toEqual({ id: 1n, balance: 11n }) + expect(await mapTest.maps.map1.get(1n)).toEqual(11n) await RemoveFromMap.execute(signer, { initialFields: { @@ -524,8 +537,10 @@ describe('contract', function () { } }) - const exist1 = (await mapTest.methods.contains({ args: { key: signer.address } })).returns - expect(exist1).toEqual(false) + expect(await mapTest.maps.map0.contains(signer.address)).toEqual(false) + expect(await mapTest.maps.map0.get(signer.address)).toEqual(undefined) + expect(await mapTest.maps.map1.contains(1n)).toEqual(false) + expect(await mapTest.maps.map1.get(1n)).toEqual(undefined) }) it('should test encode contract fields', async () => {