Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix alephium/alephium-web3#489 - Unit tests fail for map inside subcontract #490

Merged
merged 6 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .project.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,25 +199,31 @@
},
"InsertIntoMap": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "331b92fe6617c1c7ac7f742ceea1943a06717477a2bd03ca266d23c65d844de6",
"sourceCodeHash": "e15d05c08920b56b57cf23d78b734666b940e8cfe4c5d815d2676c4ce13a228d",
"bytecodeDebugPatch": "",
"codeHashDebug": ""
},
"MapTest": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "331b92fe6617c1c7ac7f742ceea1943a06717477a2bd03ca266d23c65d844de6",
"sourceCodeHash": "e15d05c08920b56b57cf23d78b734666b940e8cfe4c5d815d2676c4ce13a228d",
"bytecodeDebugPatch": "=6-2+a8=1-3+128=2-2+ea=1+2=1-2+7=10-2+4025=50+7a7e0214696e73657274206174206d617020706174683a2000=56+7a7e0214696e73657274206174206d617020706174683a2000=54+7a7e0214696e73657274206174206d617020706174683a2000=280-2+33=124+7a7e021472656d6f7665206174206d617020706174683a2000=46+7a7e021472656d6f7665206174206d617020706174683a2000=48+7a7e021472656d6f7665206174206d617020706174683a2000=96",
"codeHashDebug": "31aed0ff7b29f2cbc2d8360a83f31af4e9db00f0084a7406bd84b7745181373d"
},
"MapTestSub": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "e15d05c08920b56b57cf23d78b734666b940e8cfe4c5d815d2676c4ce13a228d",
"bytecodeDebugPatch": "",
"codeHashDebug": "755ebb4ca4c436991cc8363fedb6840abf16857a6c326983376db9e68fe8c985"
},
"MapTestWrapper": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "331b92fe6617c1c7ac7f742ceea1943a06717477a2bd03ca266d23c65d844de6",
"sourceCodeHash": "e15d05c08920b56b57cf23d78b734666b940e8cfe4c5d815d2676c4ce13a228d",
"bytecodeDebugPatch": "",
"codeHashDebug": "1d525d3e4cbd1c8f4c0431bf6881e888eeebae012a14532530097f62dd766e9a"
},
"MapValue": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "331b92fe6617c1c7ac7f742ceea1943a06717477a2bd03ca266d23c65d844de6",
"sourceCodeHash": "e15d05c08920b56b57cf23d78b734666b940e8cfe4c5d815d2676c4ce13a228d",
"bytecodeDebugPatch": "",
"codeHashDebug": ""
},
Expand Down Expand Up @@ -283,7 +289,7 @@
},
"RemoveFromMap": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "331b92fe6617c1c7ac7f742ceea1943a06717477a2bd03ca266d23c65d844de6",
"sourceCodeHash": "e15d05c08920b56b57cf23d78b734666b940e8cfe4c5d815d2676c4ce13a228d",
"bytecodeDebugPatch": "",
"codeHashDebug": ""
},
Expand Down Expand Up @@ -331,7 +337,7 @@
},
"UpdateMapValue": {
"sourceFile": "test/map.ral",
"sourceCodeHash": "331b92fe6617c1c7ac7f742ceea1943a06717477a2bd03ca266d23c65d844de6",
"sourceCodeHash": "e15d05c08920b56b57cf23d78b734666b940e8cfe4c5d815d2676c4ce13a228d",
"bytecodeDebugPatch": "",
"codeHashDebug": ""
},
Expand Down
40 changes: 40 additions & 0 deletions artifacts/test/MapTestSub.ral.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"version": "v3.10.0",
"name": "MapTestSub",
"bytecode": "0101404601030307014023d3cef11f5a14046d61703a160047441703130064130064170517041600d1a21603ce0016041605c117061600d10f2ca21600160116020f0c16060100160602",
"codeHash": "755ebb4ca4c436991cc8363fedb6840abf16857a6c326983376db9e68fe8c985",
"fieldsSig": {
"names": [
"mapTestTemplateId"
],
"types": [
"ByteVec"
],
"isMutable": [
false
]
},
"eventsSig": [],
"functions": [
{
"name": "init",
"paramNames": [
"caller",
"value"
],
"paramTypes": [
"Address",
"MapValue"
],
"paramIsMutable": [
false,
false
],
"returnTypes": [
"ByteVec"
]
}
],
"constants": [],
"enums": []
}
189 changes: 189 additions & 0 deletions artifacts/ts/MapTestSub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/* Autogenerated file. Do not edit manually. */
/* tslint:disable */
/* eslint-disable */

import {
Address,
Contract,
ContractState,
TestContractResult,
HexString,
ContractFactory,
EventSubscribeOptions,
EventSubscription,
CallContractParams,
CallContractResult,
TestContractParams,
ContractEvent,
subscribeContractEvent,
subscribeContractEvents,
testMethod,
callMethod,
multicallMethods,
fetchContractState,
Asset,
ContractInstance,
getContractEventsCurrentCount,
TestContractParamsWithoutMaps,
TestContractResultWithoutMaps,
SignExecuteContractMethodParams,
SignExecuteScriptTxResult,
signExecuteMethod,
addStdIdToFields,
encodeContractFields,
Narrow,
} from "@alephium/web3";
import { default as MapTestSubContractJson } from "../test/MapTestSub.ral.json";
import { getContractByCodeHash, registerContract } from "./contracts";
import {
AddStruct1,
AddStruct2,
Balances,
MapValue,
TokenBalance,
AllStructs,
} from "./types";

// Custom types for the contract
export namespace MapTestSubTypes {
export type Fields = {
mapTestTemplateId: HexString;
};

export type State = ContractState<Fields>;

export interface CallMethodTable {
init: {
params: CallContractParams<{ caller: Address; value: MapValue }>;
result: CallContractResult<HexString>;
};
}
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;
};
export type MulticallReturnType<Callss extends MultiCallParams[]> = {
[index in keyof Callss]: MultiCallResults<Callss[index]>;
};

export interface SignExecuteMethodTable {
init: {
params: SignExecuteContractMethodParams<{
caller: Address;
value: MapValue;
}>;
result: SignExecuteScriptTxResult;
};
}
export type SignExecuteMethodParams<T extends keyof SignExecuteMethodTable> =
SignExecuteMethodTable[T]["params"];
export type SignExecuteMethodResult<T extends keyof SignExecuteMethodTable> =
SignExecuteMethodTable[T]["result"];
}

class Factory extends ContractFactory<
MapTestSubInstance,
MapTestSubTypes.Fields
> {
encodeFields(fields: MapTestSubTypes.Fields) {
return encodeContractFields(
addStdIdToFields(this.contract, fields),
this.contract.fieldsSig,
AllStructs
);
}

at(address: string): MapTestSubInstance {
return new MapTestSubInstance(address);
}

tests = {
init: async (
params: TestContractParamsWithoutMaps<
MapTestSubTypes.Fields,
{ caller: Address; value: MapValue }
>
): Promise<TestContractResultWithoutMaps<HexString>> => {
return testMethod(this, "init", params, getContractByCodeHash);
},
};

stateForTest(
initFields: MapTestSubTypes.Fields,
asset?: Asset,
address?: string
) {
return this.stateForTest_(initFields, asset, address, undefined);
}
}

// Use this object to test and deploy the contract
export const MapTestSub = new Factory(
Contract.fromJson(
MapTestSubContractJson,
"",
"755ebb4ca4c436991cc8363fedb6840abf16857a6c326983376db9e68fe8c985",
AllStructs
)
);
registerContract(MapTestSub);

// Use this class to interact with the blockchain
export class MapTestSubInstance extends ContractInstance {
constructor(address: Address) {
super(address);
}

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

view = {
init: async (
params: MapTestSubTypes.CallMethodParams<"init">
): Promise<MapTestSubTypes.CallMethodResult<"init">> => {
return callMethod(
MapTestSub,
this,
"init",
params,
getContractByCodeHash
);
},
};

transact = {
init: async (
params: MapTestSubTypes.SignExecuteMethodParams<"init">
): Promise<MapTestSubTypes.SignExecuteMethodResult<"init">> => {
return signExecuteMethod(MapTestSub, this, "init", params);
},
};

async multicall<Calls extends MapTestSubTypes.MultiCallParams>(
calls: Calls
): Promise<MapTestSubTypes.MultiCallResults<Calls>>;
async multicall<Callss extends MapTestSubTypes.MultiCallParams[]>(
callss: Narrow<Callss>
): Promise<MapTestSubTypes.MulticallReturnType<Callss>>;
async multicall<
Callss extends
| MapTestSubTypes.MultiCallParams
| MapTestSubTypes.MultiCallParams[]
>(callss: Callss): Promise<unknown> {
return await multicallMethods(
MapTestSub,
this,
callss,
getContractByCodeHash
);
}
}
1 change: 1 addition & 0 deletions artifacts/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export * from "./FakeTokenTest";
export * from "./Greeter";
export * from "./InlineTest";
export * from "./MapTest";
export * from "./MapTestSub";
export * from "./MapTestWrapper";
export * from "./MetaData";
export * from "./NFTCollectionTest";
Expand Down
20 changes: 19 additions & 1 deletion contracts/test/map.ral
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,22 @@ Contract MapTest() {
mapping[U256, U256] map1
mapping[ByteVec, U256] map2

@using(preapprovedAssets = true)
@using(preapprovedAssets = true, checkExternalCaller = false)
pub fn insert(key: Address, value: MapValue) -> () {
map0.insert!(key, key, value)
map1.insert!(key, value.id, value.balance)
map2.insert!(key, #0011, value.balance)
}

@using(checkExternalCaller = false)
pub fn update(key: Address) -> () {
let value = map0[key]
map0[key].balance = value.balance + 1
map1[value.id] = value.balance + 1
map2[#0011] = value.balance + 1
}

@using(checkExternalCaller = false)
pub fn remove(key: Address) -> () {
let value = map0[key]
map0.remove!(key, key)
Expand Down Expand Up @@ -61,3 +63,19 @@ TxScript RemoveFromMap(mapTest: MapTest, key: Address) {
TxScript UpdateMapValue(mapTest: MapTest, key: Address) {
mapTest.update(key)
}

Contract MapTestSub(mapTestTemplateId: ByteVec) {
@using(preapprovedAssets = true, checkExternalCaller = false)
pub fn init(caller: Address, value: MapValue) -> ByteVec {
let path = b`map:` ++ toByteVec!(caller)
let (encodedImmFields, encodedMutFields) = MapTest.encodeFields!()
let mapTestId = copyCreateSubContract!{caller -> ALPH: minimalContractDeposit!()}(
path,
mapTestTemplateId,
encodedImmFields,
encodedMutFields
)
MapTest(mapTestId).insert{caller -> ALPH: mapEntryDeposit!() * 3}(caller, value)
return mapTestId
}
}
39 changes: 34 additions & 5 deletions packages/web3/src/contract/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1452,6 +1452,36 @@ function getTestExistingContracts(
return existingContracts.concat(selfMapEntries, existingMapEntries)
}

function getNewCreatedContractExceptMaps(
result: node.TestContractResult,
getContractByCodeHash: (codeHash: string) => Contract
) {
const isMapContract = (codeHash: string): boolean => {
try {
getContractByCodeHash(codeHash)
return false
} catch (error) {
if (error instanceof Error && error.message.includes('Unknown code with code hash')) {
// the contract does not exist, because it is a map item contract
return true
}
throw error
}
}

const states: node.ContractState[] = []
result.events.forEach((event) => {
if (event.eventIndex === Contract.ContractCreatedEventIndex) {
const contractAddress = event.fields[0].value as string
const contractState = result.contracts.find((c) => c.address === contractAddress)
if (contractState !== undefined && !isMapContract(contractState.codeHash)) {
states.push(contractState)
}
}
})
return states
}

export function extractMapsFromApiResult(
selfAddress: string,
params: Optional<TestContractParams, 'testArgs' | 'initialFields'>,
Expand All @@ -1461,17 +1491,16 @@ export function extractMapsFromApiResult(
): { address: string; maps: Record<string, Map<Val, Val>> }[] {
const selfMaps = params.initialMaps ?? {}
const existingContracts = params.existingContracts ?? []
const filtered = apiResult.contracts.filter(
const updatedExistingContracts = apiResult.contracts.filter(
(c) => c.address === selfAddress || existingContracts.find((s) => s.address === c.address) !== undefined
)
const newCreateContracts = getNewCreatedContractExceptMaps(apiResult, getContractByCodeHash)
const allMaps: { address: string; maps: Record<string, Map<Val, Val>> }[] = []
filtered.forEach((state) => {
updatedExistingContracts.concat(newCreateContracts).forEach((state) => {
const artifact = getContractByCodeHash(state.codeHash)
if (artifact.mapsSig !== undefined) {
const originMaps =
state.address === selfAddress
? selfMaps
: (existingContracts.find((s) => s.address === state.address) as ContractStateWithMaps).maps
state.address === selfAddress ? selfMaps : existingContracts.find((s) => s.address === state.address)?.maps
const maps = existingContractsToMaps(artifact, state.address, group, apiResult, originMaps ?? {})
allMaps.push({ address: state.address, maps })
}
Expand Down
Loading
Loading