Skip to content

Commit

Permalink
Gen code for maps
Browse files Browse the repository at this point in the history
  • Loading branch information
Lbqds committed May 10, 2024
1 parent 156dc0f commit 0758622
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 148 deletions.
14 changes: 7 additions & 7 deletions .project.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
},
"InsertIntoMap": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "c486f9e27600a65c8b6461b39cecf71fc4bd466330568a66e2652693a91b612e",
"sourceCodeHash": "f2f308aaa25fb2bfd984ce7e8f38ba846908a8c0e1a7396244d40e734327b77f",
"bytecodeDebugPatch": "",
"codeHashDebug": "",
"warnings": []
Expand All @@ -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": []
Expand Down Expand Up @@ -258,7 +258,7 @@
},
"RemoveFromMap": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "c486f9e27600a65c8b6461b39cecf71fc4bd466330568a66e2652693a91b612e",
"sourceCodeHash": "f2f308aaa25fb2bfd984ce7e8f38ba846908a8c0e1a7396244d40e734327b77f",
"bytecodeDebugPatch": "",
"codeHashDebug": "",
"warnings": []
Expand Down Expand Up @@ -300,7 +300,7 @@
},
"UpdateMapValue": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "c486f9e27600a65c8b6461b39cecf71fc4bd466330568a66e2652693a91b612e",
"sourceCodeHash": "f2f308aaa25fb2bfd984ce7e8f38ba846908a8c0e1a7396244d40e734327b77f",
"bytecodeDebugPatch": "",
"codeHashDebug": "",
"warnings": []
Expand Down
40 changes: 2 additions & 38 deletions artifacts/test/MapTest.ral.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"version": "v2.11.0",
"name": "MapTest",
"bytecode": "0005403c409c40f6411e41370103030300151600d1a2140a5f5f6d61705f5f305f5f160047441601b11602d202011600d1a2140a5f5f6d61705f5f315f5f16014044b11602d201010100010400402a0c0d0d140a5f5f6d61705f5f305f5f16004744cb1703160301000c0d0d160301011702170116020d2a0c0e0c140a5f5f6d61705f5f305f5f16004744cb010216020d2a0c0e0c140a5f5f6d61705f5f315f5f16014044cb0102010001040040240c0d0d140a5f5f6d61705f5f305f5f16004744cb1703160301000c0d0d160301011702170116000d0c140a5f5f6d61705f5f305f5f16004744cb010316000d0c140a5f5f6d61705f5f315f5f16014044cb01030100010202110c0d0d140a5f5f6d61705f5f305f5f16004744cb1701160101000c0d0d1601010102010001010107140a5f5f6d61705f5f305f5f16004744cbc502",
"codeHash": "9ff6f47837ee2d8f2cb22fe5cfeb05f64d42915aa8b30dbb96783f3e99c363f6",
"bytecode": "0003403c409c40f60103030300151600d1a2140a5f5f6d61705f5f305f5f160047441601b11602d202011600d1a2140a5f5f6d61705f5f315f5f16014044b11602d201010100010400402a0c0d0d140a5f5f6d61705f5f305f5f16004744cb1703160301000c0d0d160301011702170116020d2a0c0e0c140a5f5f6d61705f5f305f5f16004744cb010216020d2a0c0e0c140a5f5f6d61705f5f315f5f16014044cb0102010001040040240c0d0d140a5f5f6d61705f5f305f5f16004744cb1703160301000c0d0d160301011702170116000d0c140a5f5f6d61705f5f305f5f16004744cb010316000d0c140a5f5f6d61705f5f315f5f16014044cb0103",
"codeHash": "05b65f931ca86fb21d222e22007c606c9de7a1d3cb602dd56df21e7b431efac5",
"fieldsSig": {
"names": [],
"types": [],
Expand Down Expand Up @@ -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": [],
Expand Down
97 changes: 8 additions & 89 deletions artifacts/ts/MapTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ContractState<any>, "fields">;

export interface CallMethodTable {
get: {
params: CallContractParams<{ key: Address }>;
result: CallContractResult<MapValue>;
};
contains: {
params: CallContractParams<{ key: Address }>;
result: CallContractResult<boolean>;
};
}
export type CallMethodParams<T extends keyof CallMethodTable> =
CallMethodTable[T]["params"];
export type CallMethodResult<T extends keyof CallMethodTable> =
CallMethodTable[T]["result"];
export type MultiCallParams = Partial<{
[Name in keyof CallMethodTable]: CallMethodTable[Name]["params"];
}>;
export type MultiCallResults<T extends MultiCallParams> = {
[MaybeName in keyof T]: MaybeName extends keyof CallMethodTable
? CallMethodTable[MaybeName]["result"]
: undefined;
};
}

class Factory extends ContractFactory<MapTestInstance, {}> {
Expand Down Expand Up @@ -121,49 +99,15 @@ class Factory extends ContractFactory<MapTestInstance, {}> {
> => {
return testMethod(this, "remove", params);
},
get: async (
params: Omit<
TestContractParams<
never,
{ key: Address },
{ map0?: Map<Address, MapValue>; map1?: Map<bigint, bigint> }
>,
"initialFields"
>
): Promise<
TestContractResult<
MapValue,
{ map0?: Map<Address, MapValue>; map1?: Map<bigint, bigint> }
>
> => {
return testMethod(this, "get", params);
},
contains: async (
params: Omit<
TestContractParams<
never,
{ key: Address },
{ map0?: Map<Address, MapValue>; map1?: Map<bigint, bigint> }
>,
"initialFields"
>
): Promise<
TestContractResult<
boolean,
{ map0?: Map<Address, MapValue>; map1?: Map<bigint, bigint> }
>
> => {
return testMethod(this, "contains", params);
},
};
}

// Use this object to test and deploy the contract
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
)
);
Expand All @@ -174,37 +118,12 @@ export class MapTestInstance extends ContractInstance {
super(address);
}

async fetchState(): Promise<MapTestTypes.State> {
return fetchContractState(MapTest, this);
}

methods = {
get: async (
params: MapTestTypes.CallMethodParams<"get">
): Promise<MapTestTypes.CallMethodResult<"get">> => {
return callMethod(MapTest, this, "get", params, getContractByCodeHash);
},
contains: async (
params: MapTestTypes.CallMethodParams<"contains">
): Promise<MapTestTypes.CallMethodResult<"contains">> => {
return callMethod(
MapTest,
this,
"contains",
params,
getContractByCodeHash
);
},
maps = {
map0: new RalphMap<Address, MapValue>(MapTest.contract, this, "map0"),
map1: new RalphMap<bigint, bigint>(MapTest.contract, this, "map1"),
};

async multicall<Calls extends MapTestTypes.MultiCallParams>(
calls: Calls
): Promise<MapTestTypes.MultiCallResults<Calls>> {
return (await multicallMethods(
MapTest,
this,
calls,
getContractByCodeHash
)) as MapTestTypes.MultiCallResults<Calls>;
async fetchState(): Promise<MapTestTypes.State> {
return fetchContractState(MapTest, this);
}
}
8 changes: 0 additions & 8 deletions contracts/test/map.ral
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
18 changes: 18 additions & 0 deletions packages/cli/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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')}
Expand Down
50 changes: 50 additions & 0 deletions packages/web3/src/contract/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2007,6 +2007,56 @@ export async function testMethod<
} as TestContractResult<R, M>
}

export class RalphMap<K extends Val, V extends Val> {
constructor(
private readonly parentContract: Contract,
private readonly parentInstance: ContractInstance,
private readonly mapName: string
) {}

async get(key: K): Promise<V | undefined> {
return getMapItem(this.parentContract, this.parentInstance, this.mapName, key)
}

async contains(key: K): Promise<boolean> {
return this.get(key).then((v) => v !== undefined)
}
}

export async function getMapItem<R extends Val>(
parentContract: Contract,
parentInstance: ContractInstance,
mapName: string,
key: Val
): Promise<R | undefined> {
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<Val, Val>
Expand Down
27 changes: 21 additions & 6 deletions test/contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
RemoveFromMap,
TemplateArrayVar,
TestAssert,
UpdateMapValue,
UpdateUserAccount
} from '../artifacts/ts/scripts'
import { Sub, SubTypes } from '../artifacts/ts/Sub'
Expand Down Expand Up @@ -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: {
Expand All @@ -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 () => {
Expand Down

0 comments on commit 0758622

Please sign in to comment.