diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..e5a601bf --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +COMPOSE_FILE := local-env.yml +LOGS_DIR := logs +SERVICES := proof-server indexer node + +.PHONY: env-up env-down env-logs env-logs-clean env-status + +## Start local environment and stream logs to logs/ +env-up: env-down + docker compose -f $(COMPOSE_FILE) up -d + @mkdir -p $(LOGS_DIR) + @for svc in $(SERVICES); do \ + docker compose -f $(COMPOSE_FILE) logs -f --no-log-prefix $$svc > $(LOGS_DIR)/$$svc.log 2>&1 & \ + done + @echo "Logs streaming to $(LOGS_DIR)/" + +## Stop local environment +env-down: + @-pkill -f "docker compose -f $(COMPOSE_FILE) logs" 2>/dev/null || true + docker compose -f $(COMPOSE_FILE) down + +## Tail all logs +env-logs: + tail -f $(LOGS_DIR)/*.log + +## Clear log files +env-logs-clean: + rm -rf $(LOGS_DIR)/*.log + +## Show container status +env-status: + docker compose -f $(COMPOSE_FILE) ps diff --git a/contracts/package.json b/contracts/package.json index 91b68ba1..7796032d 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -34,6 +34,7 @@ "build": "compact-builder --hierarchical --out dist --clean-dist --exclude '*/archive/*' --exclude 'Mock*' --exclude '*.mock.compact' --copy package.json --copy ../README.md && find dist -type d -empty -delete", "test": "SKIP_ZK=true yarn run compact && vitest run", "test:coverage": "SKIP_ZK=true yarn run compact && vitest run --coverage", + "test:live": "yarn run compact && MIDNIGHT_BACKEND=live vitest run --config vitest.live.config.ts", "compact:integration": "SKIP_ZK=true compact compile test/integration/_mocks/SharedInitCollision.compact artifacts/SharedInitCollision && SKIP_ZK=true compact compile test/integration/_mocks/ComposedTokens.compact artifacts/ComposedTokens", "test:integration": "yarn run compact:integration && vitest run --config vitest.integration.config.ts", "types": "tsc -p tsconfig.json --noEmit", @@ -46,7 +47,7 @@ "@openzeppelin/compact-cli": "^0.0.2" }, "devDependencies": { - "@openzeppelin/compact-simulator": "^0.1.0", + "@openzeppelin/compact-simulator": "^0.2.0", "@tsconfig/node24": "^24.0.4", "@types/node": "25.9.3", "@vitest/coverage-v8": "^4.1.9", diff --git a/contracts/src/access/test/AccessControl.test.ts b/contracts/src/access/test/AccessControl.test.ts index a8d28ec6..7e6e9410 100644 --- a/contracts/src/access/test/AccessControl.test.ts +++ b/contracts/src/access/test/AccessControl.test.ts @@ -78,81 +78,93 @@ const operatorTypes = [ ] as const; describe('AccessControl', () => { - beforeEach(() => { - accessControl = new AccessControlSimulator(); + beforeEach(async () => { + accessControl = await AccessControlSimulator.create(); }); describe('hasRole', () => { - beforeEach(() => { - accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); + beforeEach(async () => { + await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); }); - it('should return true when operator has a role', () => { - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + it('should return true when operator has a role', async () => { + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); }); - it('should return false when unauthorized', () => { - expect(accessControl.hasRole(OPERATOR_ROLE_1, UNAUTHORIZED.either)).toBe( - false, - ); + it('should return false when unauthorized', async () => { + expect( + await accessControl.hasRole(OPERATOR_ROLE_1, UNAUTHORIZED.either), + ).toBe(false); }); - it('should return false when role does not exist', () => { - expect(accessControl.hasRole(UNINITIALIZED_ROLE, OP1.either)).toBe(false); + it('should return false when role does not exist', async () => { + expect(await accessControl.hasRole(UNINITIALIZED_ROLE, OP1.either)).toBe( + false, + ); }); - it('should return true when queried with dirty Either (canonicalization)', () => { + it('should return true when queried with dirty Either (canonicalization)', async () => { const dirtyEither = { is_left: true, left: OP1.accountId, right: { bytes: new Uint8Array(32).fill(0xff) }, }; - expect(accessControl.hasRole(OPERATOR_ROLE_1, dirtyEither)).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, dirtyEither)).toBe( + true, + ); }); - it('should return false when dirty Either has wrong accountId', () => { + it('should return false when dirty Either has wrong accountId', async () => { const dirtyEither = { is_left: true, left: UNAUTHORIZED.accountId, right: { bytes: new Uint8Array(32).fill(0xff) }, }; - expect(accessControl.hasRole(OPERATOR_ROLE_1, dirtyEither)).toBe(false); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, dirtyEither)).toBe( + false, + ); }); - it('should match hasRole with dirty left side on contract address', () => { - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); + it('should match hasRole with dirty left side on contract address', async () => { + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); const dirtyContract = { is_left: false, left: new Uint8Array(32).fill(0xff), right: OP1_CONTRACT.right, }; - expect(accessControl.hasRole(OPERATOR_ROLE_1, dirtyContract)).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, dirtyContract)).toBe( + true, + ); }); }); describe('assertOnlyRole', () => { - beforeEach(() => { - accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); + beforeEach(async () => { + await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); }); - it('should allow operator with role to call', () => { + it('should allow operator with role to call', async () => { // Set secret key for OP1 - accessControl.privateState.injectSecretKey(OP1.secretKey); + await accessControl.privateState.injectSecretKey(OP1.secretKey); - expect(() => accessControl.assertOnlyRole(OPERATOR_ROLE_1)).not.toThrow(); + await expect( + accessControl.assertOnlyRole(OPERATOR_ROLE_1), + ).resolves.not.toThrow(); }); - it('should fail if caller is unauthorized', () => { + it('should fail if caller is unauthorized', async () => { // Set bad secret key - accessControl.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await accessControl.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => accessControl.assertOnlyRole(OPERATOR_ROLE_1)).toThrow( - 'AccessControl: unauthorized account', - ); + await expect( + accessControl.assertOnlyRole(OPERATOR_ROLE_1), + ).rejects.toThrow('AccessControl: unauthorized account'); }); - it('should fail when contract address matches accountId bytes but is right variant', () => { + it('should fail when contract address matches accountId bytes but is right variant', async () => { // Grant role to a contract address whose bytes match ADMIN's accountId const contractWithSameBytes = { is_left: false, @@ -160,510 +172,591 @@ describe('AccessControl', () => { right: { bytes: ADMIN.accountId }, }; - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, contractWithSameBytes); + await accessControl._unsafeGrantRole( + OPERATOR_ROLE_1, + contractWithSameBytes, + ); expect( - accessControl.hasRole(OPERATOR_ROLE_1, contractWithSameBytes), + await accessControl.hasRole(OPERATOR_ROLE_1, contractWithSameBytes), ).toBe(true); // ADMIN's witness produces left(H(sk)) which has the same bytes // but is a different Either variant than the granted role - accessControl.privateState.injectSecretKey(ADMIN.secretKey); - expect(() => { - accessControl.assertOnlyRole(OPERATOR_ROLE_1); - }).toThrow('AccessControl: unauthorized account'); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await expect( + accessControl.assertOnlyRole(OPERATOR_ROLE_1), + ).rejects.toThrow('AccessControl: unauthorized account'); }); }); describe('_checkRole', () => { - beforeEach(() => { - accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); + beforeEach(async () => { + await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); }); - it('should not fail if user has role', () => { - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + it('should not fail if user has role', async () => { + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); - expect(() => + await expect( accessControl._checkRole(OPERATOR_ROLE_1, OP1.either), - ).not.toThrow(); + ).resolves.not.toThrow(); }); - it('should not fail if contract has role', () => { - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1_CONTRACT)).toBe(true); + it('should not fail if contract has role', async () => { + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1_CONTRACT)).toBe( + true, + ); - expect(() => + await expect( accessControl._checkRole(OPERATOR_ROLE_1, OP1_CONTRACT), - ).not.toThrow(); + ).resolves.not.toThrow(); }); - it('should fail if operator is unauthorized', () => { - expect(() => + it('should fail if operator is unauthorized', async () => { + await expect( accessControl._checkRole(OPERATOR_ROLE_1, UNAUTHORIZED.either), - ).toThrow('AccessControl: unauthorized account'); + ).rejects.toThrow('AccessControl: unauthorized account'); }); }); describe('DEFAULT_ADMIN_ROLE', () => { - it('should return zero bytes', () => { - expect(accessControl.DEFAULT_ADMIN_ROLE()).toEqual(DEFAULT_ADMIN_ROLE); + it('should return zero bytes', async () => { + expect(await accessControl.DEFAULT_ADMIN_ROLE()).toEqual( + DEFAULT_ADMIN_ROLE, + ); }); }); describe('getRoleAdmin', () => { - it('should return default admin role if admin role not set', () => { - expect(accessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( + it('should return default admin role if admin role not set', async () => { + expect(await accessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( DEFAULT_ADMIN_ROLE, ); }); - it('should return custom admin role if set', () => { - accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); - expect(accessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( + it('should return custom admin role if set', async () => { + await accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); + expect(await accessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( CUSTOM_ADMIN_ROLE, ); }); }); describe('grantRole', () => { - beforeEach(() => { - accessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN.either); + beforeEach(async () => { + await accessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN.either); }); - it('admin should grant role', () => { + it('admin should grant role', async () => { // Set admin SK - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + await accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); }); - it('admin should grant multiple roles', () => { + it('admin should grant multiple roles', async () => { // Set admin SK - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); for (let i = 0; i < operatorRolesList.length; i++) { for (let j = 0; j < commitmentOperators.length; j++) { - accessControl.grantRole(operatorRolesList[i], commitmentOperators[j]); + await accessControl.grantRole( + operatorRolesList[i], + commitmentOperators[j], + ); expect( - accessControl.hasRole(operatorRolesList[i], commitmentOperators[j]), + await accessControl.hasRole( + operatorRolesList[i], + commitmentOperators[j], + ), ).toBe(true); } } }); - it('should fail if unauthorized grants role', () => { + it('should fail if unauthorized grants role', async () => { // Set unauthorized SK - accessControl.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await accessControl.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => { - accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); - }).toThrow('AccessControl: unauthorized account'); + await expect( + accessControl.grantRole(OPERATOR_ROLE_1, OP1.either), + ).rejects.toThrow('AccessControl: unauthorized account'); }); - it('should fail if operator grants role', () => { + it('should fail if operator grants role', async () => { // Set admin SK - accessControl.privateState.injectSecretKey(ADMIN.secretKey); - accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); // Set OP1 SK - accessControl.privateState.injectSecretKey(OP1.secretKey); + await accessControl.privateState.injectSecretKey(OP1.secretKey); - expect(() => { - accessControl.grantRole(OPERATOR_ROLE_1, OP2.either); - }).toThrow('AccessControl: unauthorized account'); + await expect( + accessControl.grantRole(OPERATOR_ROLE_1, OP2.either), + ).rejects.toThrow('AccessControl: unauthorized account'); }); - it('should fail if admin grants role to ContractAddress', () => { + it('should fail if admin grants role to ContractAddress', async () => { // Set admin SK - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - expect(() => { - accessControl.grantRole(OPERATOR_ROLE_1, OP1_CONTRACT); - }).toThrow('AccessControl: unsafe role approval'); + await expect( + accessControl.grantRole(OPERATOR_ROLE_1, OP1_CONTRACT), + ).rejects.toThrow('AccessControl: unsafe role approval'); }); - it('admin should not be able to grant after self-revocation', () => { + it('admin should not be able to grant after self-revocation', async () => { // Set admin SK - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - accessControl.revokeRole(DEFAULT_ADMIN_ROLE, ADMIN.either); + await accessControl.revokeRole(DEFAULT_ADMIN_ROLE, ADMIN.either); - expect(() => + await expect( accessControl.grantRole(OPERATOR_ROLE_1, OP1.either), - ).toThrow('AccessControl: unauthorized account'); + ).rejects.toThrow('AccessControl: unauthorized account'); }); - it('admin should not be able to grant after renouncing role', () => { + it('admin should not be able to grant after renouncing role', async () => { // Set admin SK - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - accessControl.renounceRole(DEFAULT_ADMIN_ROLE, ADMIN.either); + await accessControl.renounceRole(DEFAULT_ADMIN_ROLE, ADMIN.either); - expect(() => + await expect( accessControl.grantRole(OPERATOR_ROLE_1, OP1.either), - ).toThrow('AccessControl: unauthorized account'); + ).rejects.toThrow('AccessControl: unauthorized account'); }); - it('admin authority should not be transitive across role hierarchies', () => { - accessControl._setRoleAdmin(OPERATOR_ROLE_2, OPERATOR_ROLE_1); - accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); + it('admin authority should not be transitive across role hierarchies', async () => { + await accessControl._setRoleAdmin(OPERATOR_ROLE_2, OPERATOR_ROLE_1); + await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); // ADMIN holds DEFAULT_ADMIN_ROLE but not OPERATOR_ROLE_1 - expect(() => + await expect( accessControl.grantRole(OPERATOR_ROLE_2, OP2.either), - ).toThrow('AccessControl: unauthorized account'); + ).rejects.toThrow('AccessControl: unauthorized account'); // OP1 holds OPERATOR_ROLE_1 which is admin of OPERATOR_ROLE_2 - accessControl.privateState.injectSecretKey(OP1.secretKey); - expect(() => + await accessControl.privateState.injectSecretKey(OP1.secretKey); + await expect( accessControl.grantRole(OPERATOR_ROLE_2, OP2.either), - ).not.toThrow(); + ).resolves.not.toThrow(); }); - it('admin should re-grant a role after revoking it', () => { - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + it('admin should re-grant a role after revoking it', async () => { + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + await accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); - accessControl.revokeRole(OPERATOR_ROLE_1, OP1.either); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(false); + await accessControl.revokeRole(OPERATOR_ROLE_1, OP1.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + false, + ); - accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + await accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); }); - it('should be idempotent when granting an already-held role', () => { - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + it('should be idempotent when granting an already-held role', async () => { + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + await accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); // Second grant should not throw or corrupt state - expect(() => + await expect( accessControl.grantRole(OPERATOR_ROLE_1, OP1.either), - ).not.toThrow(); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + ).resolves.not.toThrow(); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); // Revoke should still work normally after double-grant - accessControl.revokeRole(OPERATOR_ROLE_1, OP1.either); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(false); + await accessControl.revokeRole(OPERATOR_ROLE_1, OP1.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + false, + ); }); }); describe('revokeRole', () => { - beforeEach(() => { - accessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN.either); - accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); + beforeEach(async () => { + await accessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN.either); + await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); }); describe.each( operatorTypes, )('when the operator is a %s', (_operatorType, _operator) => { - it('admin should revoke role', () => { + it('admin should revoke role', async () => { // Set admin SK - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - accessControl.revokeRole(OPERATOR_ROLE_1, _operator); - expect(accessControl.hasRole(OPERATOR_ROLE_1, _operator)).toBe(false); + await accessControl.revokeRole(OPERATOR_ROLE_1, _operator); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, _operator)).toBe( + false, + ); }); }); - it('should fail if unauthorized revokes role', () => { - accessControl.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + it('should fail if unauthorized revokes role', async () => { + await accessControl.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => { - accessControl.revokeRole(OPERATOR_ROLE_1, OP1.either); - }).toThrow('AccessControl: unauthorized account'); + await expect( + accessControl.revokeRole(OPERATOR_ROLE_1, OP1.either), + ).rejects.toThrow('AccessControl: unauthorized account'); }); - it('should fail if operator revokes role', () => { - accessControl.privateState.injectSecretKey(OP1.secretKey); + it('should fail if operator revokes role', async () => { + await accessControl.privateState.injectSecretKey(OP1.secretKey); - expect(() => { - accessControl.revokeRole(OPERATOR_ROLE_1, OP2.either); - }).toThrow('AccessControl: unauthorized account'); + await expect( + accessControl.revokeRole(OPERATOR_ROLE_1, OP2.either), + ).rejects.toThrow('AccessControl: unauthorized account'); }); - it('admin should revoke multiple roles', () => { - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + it('admin should revoke multiple roles', async () => { + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); for (let i = 0; i < operatorRolesList.length; i++) { for (let j = 0; j < allOperators.length; j++) { - accessControl._unsafeGrantRole(operatorRolesList[i], allOperators[j]); - accessControl.revokeRole(operatorRolesList[i], allOperators[j]); + await accessControl._unsafeGrantRole( + operatorRolesList[i], + allOperators[j], + ); + await accessControl.revokeRole(operatorRolesList[i], allOperators[j]); expect( - accessControl.hasRole(operatorRolesList[i], allOperators[j]), + await accessControl.hasRole(operatorRolesList[i], allOperators[j]), ).toBe(false); } } }); - it('should not corrupt state when revoking a never-granted role', () => { - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + it('should not corrupt state when revoking a never-granted role', async () => { + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); // Revoke a role that was never granted to OP2 - accessControl.revokeRole(OPERATOR_ROLE_2, OP2.either); - expect(accessControl.hasRole(OPERATOR_ROLE_2, OP2.either)).toBe(false); + await accessControl.revokeRole(OPERATOR_ROLE_2, OP2.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_2, OP2.either)).toBe( + false, + ); // Subsequent grant should still work - accessControl.grantRole(OPERATOR_ROLE_2, OP2.either); - expect(accessControl.hasRole(OPERATOR_ROLE_2, OP2.either)).toBe(true); + await accessControl.grantRole(OPERATOR_ROLE_2, OP2.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_2, OP2.either)).toBe( + true, + ); }); }); describe('renounceRole', () => { - beforeEach(() => { - accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); + beforeEach(async () => { + await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); }); - it('should allow operator to renounce own role', () => { - accessControl.privateState.injectSecretKey(OP1.secretKey); + it('should allow operator to renounce own role', async () => { + await accessControl.privateState.injectSecretKey(OP1.secretKey); - accessControl.renounceRole(OPERATOR_ROLE_1, OP1.either); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(false); + await accessControl.renounceRole(OPERATOR_ROLE_1, OP1.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + false, + ); }); // Should be refactored with c2c - it('should fail when renouncing as a ContractAddress', () => { - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); + it('should fail when renouncing as a ContractAddress', async () => { + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - expect(() => { - accessControl.renounceRole(OPERATOR_ROLE_1, OP1_CONTRACT); - }).toThrow('AccessControl: bad confirmation'); + await expect( + accessControl.renounceRole(OPERATOR_ROLE_1, OP1_CONTRACT), + ).rejects.toThrow('AccessControl: bad confirmation'); }); - it('should fail when unauthorized renounces role', () => { - accessControl.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + it('should fail when unauthorized renounces role', async () => { + await accessControl.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => { - accessControl.renounceRole(OPERATOR_ROLE_1, OP1.either); - }).toThrow('AccessControl: bad confirmation'); + await expect( + accessControl.renounceRole(OPERATOR_ROLE_1, OP1.either), + ).rejects.toThrow('AccessControl: bad confirmation'); }); - it('should not fail when renouncing a role not held', () => { - accessControl.privateState.injectSecretKey(OP1.secretKey); + it('should not fail when renouncing a role not held', async () => { + await accessControl.privateState.injectSecretKey(OP1.secretKey); // Confirm role not already held - expect(accessControl.hasRole(OPERATOR_ROLE_3, OP1.either)).toBe(false); + expect(await accessControl.hasRole(OPERATOR_ROLE_3, OP1.either)).toBe( + false, + ); - accessControl.renounceRole(OPERATOR_ROLE_3, OP1.either); - expect(accessControl.hasRole(OPERATOR_ROLE_3, OP1.either)).toBe(false); + await accessControl.renounceRole(OPERATOR_ROLE_3, OP1.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_3, OP1.either)).toBe( + false, + ); }); }); describe('_setRoleAdmin', () => { - beforeEach(() => { - accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); + beforeEach(async () => { + await accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); }); - it('should set role admin', () => { - expect(accessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( + it('should set role admin', async () => { + expect(await accessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( CUSTOM_ADMIN_ROLE, ); }); - it('should set multiple role admins', () => { - accessControl._setRoleAdmin(OPERATOR_ROLE_2, CUSTOM_ADMIN_ROLE); - accessControl._setRoleAdmin(OPERATOR_ROLE_3, CUSTOM_ADMIN_ROLE); + it('should set multiple role admins', async () => { + await accessControl._setRoleAdmin(OPERATOR_ROLE_2, CUSTOM_ADMIN_ROLE); + await accessControl._setRoleAdmin(OPERATOR_ROLE_3, CUSTOM_ADMIN_ROLE); - expect(accessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( + expect(await accessControl.getRoleAdmin(OPERATOR_ROLE_1)).toEqual( CUSTOM_ADMIN_ROLE, ); - expect(accessControl.getRoleAdmin(OPERATOR_ROLE_2)).toEqual( + expect(await accessControl.getRoleAdmin(OPERATOR_ROLE_2)).toEqual( CUSTOM_ADMIN_ROLE, ); - expect(accessControl.getRoleAdmin(OPERATOR_ROLE_3)).toEqual( + expect(await accessControl.getRoleAdmin(OPERATOR_ROLE_3)).toEqual( CUSTOM_ADMIN_ROLE, ); }); - it('should authorize new admin to grant / revoke roles', () => { - accessControl._grantRole(CUSTOM_ADMIN_ROLE, CUSTOM_ADMIN.either); - accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); + it('should authorize new admin to grant / revoke roles', async () => { + await accessControl._grantRole(CUSTOM_ADMIN_ROLE, CUSTOM_ADMIN.either); + await accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); // Set custom admin SK - accessControl.privateState.injectSecretKey(CUSTOM_ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(CUSTOM_ADMIN.secretKey); // Grant role and check it's been granted - expect(() => + await expect( accessControl.grantRole(OPERATOR_ROLE_1, OP1.either), - ).not.toThrow(); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + ).resolves.not.toThrow(); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); // Revoke role and check it's been revoked - expect(() => + await expect( accessControl.revokeRole(OPERATOR_ROLE_1, OP1.either), - ).not.toThrow(); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(false); + ).resolves.not.toThrow(); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + false, + ); }); - it('should disallow previous admin from granting / revoking roles', () => { - accessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN.either); - accessControl._grantRole(CUSTOM_ADMIN_ROLE, CUSTOM_ADMIN.either); - accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); + it('should disallow previous admin from granting / revoking roles', async () => { + await accessControl._grantRole(DEFAULT_ADMIN_ROLE, ADMIN.either); + await accessControl._grantRole(CUSTOM_ADMIN_ROLE, CUSTOM_ADMIN.either); + await accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); // Set init admin - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - expect(() => { - accessControl.grantRole(OPERATOR_ROLE_1, OP1.either); - }).toThrow('AccessControl: unauthorized account'); + await expect( + accessControl.grantRole(OPERATOR_ROLE_1, OP1.either), + ).rejects.toThrow('AccessControl: unauthorized account'); - expect(() => { - accessControl.revokeRole(OPERATOR_ROLE_1, OP1.either); - }).toThrow('AccessControl: unauthorized account'); + await expect( + accessControl.revokeRole(OPERATOR_ROLE_1, OP1.either), + ).rejects.toThrow('AccessControl: unauthorized account'); }); - it('should allow overwriting admin role and transfer authority', () => { + it('should allow overwriting admin role and transfer authority', async () => { const NEW_ADMIN_ROLE = convertFieldToBytes(32, 99n, ''); const NEW_ADMIN = makeUser('NEW_ADMIN'); - accessControl._grantRole(CUSTOM_ADMIN_ROLE, CUSTOM_ADMIN.either); - accessControl._grantRole(NEW_ADMIN_ROLE, NEW_ADMIN.either); - accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); + await accessControl._grantRole(CUSTOM_ADMIN_ROLE, CUSTOM_ADMIN.either); + await accessControl._grantRole(NEW_ADMIN_ROLE, NEW_ADMIN.either); + await accessControl._setRoleAdmin(OPERATOR_ROLE_1, CUSTOM_ADMIN_ROLE); // CUSTOM_ADMIN can grant - accessControl.privateState.injectSecretKey(CUSTOM_ADMIN.secretKey); - expect(() => + await accessControl.privateState.injectSecretKey(CUSTOM_ADMIN.secretKey); + await expect( accessControl.grantRole(OPERATOR_ROLE_1, OP1.either), - ).not.toThrow(); + ).resolves.not.toThrow(); // Overwrite admin role - accessControl._setRoleAdmin(OPERATOR_ROLE_1, NEW_ADMIN_ROLE); + await accessControl._setRoleAdmin(OPERATOR_ROLE_1, NEW_ADMIN_ROLE); // CUSTOM_ADMIN should lose authority - expect(() => + await expect( accessControl.grantRole(OPERATOR_ROLE_1, OP2.either), - ).toThrow('AccessControl: unauthorized account'); + ).rejects.toThrow('AccessControl: unauthorized account'); // NEW_ADMIN should gain authority - accessControl.privateState.injectSecretKey(NEW_ADMIN.secretKey); - expect(() => + await accessControl.privateState.injectSecretKey(NEW_ADMIN.secretKey); + await expect( accessControl.grantRole(OPERATOR_ROLE_1, OP2.either), - ).not.toThrow(); + ).resolves.not.toThrow(); }); }); describe('_grantRole', () => { - it('should grant role', () => { - expect(accessControl._grantRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + it('should grant role', async () => { + expect(await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); }); - it('should return false if hasRole already', () => { - expect(accessControl._grantRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + it('should return false if hasRole already', async () => { + expect(await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); - expect(accessControl._grantRole(OPERATOR_ROLE_1, OP1.either)).toBe(false); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + expect(await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either)).toBe( + false, + ); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); }); // Should be refactored with c2c - it('should fail to grant role to a ContractAddress', () => { - expect(() => { - accessControl._grantRole(OPERATOR_ROLE_1, OP1_CONTRACT); - }).toThrow('AccessControl: unsafe role approval'); + it('should fail to grant role to a ContractAddress', async () => { + await expect( + accessControl._grantRole(OPERATOR_ROLE_1, OP1_CONTRACT), + ).rejects.toThrow('AccessControl: unsafe role approval'); }); - it('should grant multiple roles', () => { + it('should grant multiple roles', async () => { for (let i = 0; i < operatorRolesList.length; i++) { for (let j = 0; j < commitmentOperators.length; j++) { - accessControl._grantRole( + await accessControl._grantRole( operatorRolesList[i], commitmentOperators[j], ); expect( - accessControl.hasRole(operatorRolesList[i], commitmentOperators[j]), + await accessControl.hasRole( + operatorRolesList[i], + commitmentOperators[j], + ), ).toBe(true); } } }); - it('should allow regranting a revoked role', () => { - accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); - accessControl._revokeRole(OPERATOR_ROLE_1, OP1.either); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(false); + it('should allow regranting a revoked role', async () => { + await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either); + await accessControl._revokeRole(OPERATOR_ROLE_1, OP1.either); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + false, + ); - expect(accessControl._grantRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); + expect(await accessControl._grantRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, + ); }); }); describe('_unsafeGrantRole', () => { - it('should grant role', () => { - expect(accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either)).toBe( + it('should grant role', async () => { + expect( + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either), + ).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( true, ); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); }); - it('should return false if hasRole already', () => { - expect(accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either)).toBe( + it('should return false if hasRole already', async () => { + expect( + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either), + ).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( true, ); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); - expect(accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either)).toBe( - false, + expect( + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either), + ).toBe(false); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + true, ); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(true); }); // Should be refactored with c2c - it('should grant role to a ContractAddress', () => { + it('should grant role to a ContractAddress', async () => { expect( - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT), + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT), ).toBe(true); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1_CONTRACT)).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1_CONTRACT)).toBe( + true, + ); }); - it('should grant multiple roles', () => { + it('should grant multiple roles', async () => { for (let i = 0; i < operatorRolesList.length; i++) { for (let j = 0; j < allOperators.length; j++) { expect( - accessControl._unsafeGrantRole( + await accessControl._unsafeGrantRole( operatorRolesList[i], allOperators[j], ), ).toBe(true); expect( - accessControl.hasRole(operatorRolesList[i], allOperators[j]), + await accessControl.hasRole(operatorRolesList[i], allOperators[j]), ).toBe(true); } } }); - it('should match on subsequent hasRole with clean Either after dirty grant', () => { + it('should match on subsequent hasRole with clean Either after dirty grant', async () => { const dirtyEither = { is_left: true, left: OP2.accountId, right: { bytes: new Uint8Array(32).fill(0xff) }, }; - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, dirtyEither); + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, dirtyEither); // Clean Either should find the role - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP2.either)).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP2.either)).toBe( + true, + ); }); - it('should match on subsequent hasRole with dirty Either after clean grant', () => { - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP2.either); + it('should match on subsequent hasRole with dirty Either after clean grant', async () => { + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP2.either); const dirtyEither = { is_left: true, left: OP2.accountId, right: { bytes: new Uint8Array(32).fill(0xff) }, }; - expect(accessControl.hasRole(OPERATOR_ROLE_1, dirtyEither)).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, dirtyEither)).toBe( + true, + ); }); - it('should return false for duplicate grant with dirty Either', () => { + it('should return false for duplicate grant with dirty Either', async () => { // Init granted role - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either); + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either); const dirtyEither = { is_left: true, @@ -672,9 +765,9 @@ describe('AccessControl', () => { }; // Dirty Either should still detect the existing grant - expect(accessControl._unsafeGrantRole(OPERATOR_ROLE_1, dirtyEither)).toBe( - false, - ); + expect( + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, dirtyEither), + ).toBe(false); }); }); @@ -682,37 +775,45 @@ describe('AccessControl', () => { describe.each( operatorTypes, )('when the operator is a %s', (_, _operator) => { - it('should revoke role', () => { - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, _operator); - expect(accessControl._revokeRole(OPERATOR_ROLE_1, _operator)).toBe( - true, + it('should revoke role', async () => { + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, _operator); + expect( + await accessControl._revokeRole(OPERATOR_ROLE_1, _operator), + ).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, _operator)).toBe( + false, ); - expect(accessControl.hasRole(OPERATOR_ROLE_1, _operator)).toBe(false); }); }); - it('should return false if account does not have role', () => { - expect(accessControl._revokeRole(OPERATOR_ROLE_1, OP1.either)).toBe( + it('should return false if account does not have role', async () => { + expect(await accessControl._revokeRole(OPERATOR_ROLE_1, OP1.either)).toBe( false, ); }); - it('should revoke multiple roles', () => { + it('should revoke multiple roles', async () => { for (let i = 0; i < operatorRolesList.length; i++) { for (let j = 0; j < allOperators.length; j++) { - accessControl._unsafeGrantRole(operatorRolesList[i], allOperators[j]); + await accessControl._unsafeGrantRole( + operatorRolesList[i], + allOperators[j], + ); expect( - accessControl._revokeRole(operatorRolesList[i], allOperators[j]), + await accessControl._revokeRole( + operatorRolesList[i], + allOperators[j], + ), ).toBe(true); expect( - accessControl.hasRole(operatorRolesList[i], allOperators[j]), + await accessControl.hasRole(operatorRolesList[i], allOperators[j]), ).toBe(false); } } }); - it('should revoke with dirty Either after clean grant', () => { - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either); + it('should revoke with dirty Either after clean grant', async () => { + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1.either); const dirtyEither = { is_left: true, @@ -720,61 +821,65 @@ describe('AccessControl', () => { right: { bytes: new Uint8Array(32).fill(0xff) }, }; - expect(accessControl._revokeRole(OPERATOR_ROLE_1, dirtyEither)).toBe( - true, + expect( + await accessControl._revokeRole(OPERATOR_ROLE_1, dirtyEither), + ).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe( + false, ); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1.either)).toBe(false); }); - it('should return false when revoking ungranted role with dirty Either', () => { + it('should return false when revoking ungranted role with dirty Either', async () => { const dirtyEither = { is_left: true, left: OP2.accountId, right: { bytes: new Uint8Array(32).fill(0xff) }, }; - expect(accessControl._revokeRole(OPERATOR_ROLE_1, dirtyEither)).toBe( - false, - ); + expect( + await accessControl._revokeRole(OPERATOR_ROLE_1, dirtyEither), + ).toBe(false); }); - it('should revoke with dirty left side on contract address', () => { - accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); + it('should revoke with dirty left side on contract address', async () => { + await accessControl._unsafeGrantRole(OPERATOR_ROLE_1, OP1_CONTRACT); const dirtyContract = { is_left: false, left: new Uint8Array(32).fill(0xff), right: OP1_CONTRACT.right, }; - expect(accessControl._revokeRole(OPERATOR_ROLE_1, dirtyContract)).toBe( - true, + expect( + await accessControl._revokeRole(OPERATOR_ROLE_1, dirtyContract), + ).toBe(true); + expect(await accessControl.hasRole(OPERATOR_ROLE_1, OP1_CONTRACT)).toBe( + false, ); - expect(accessControl.hasRole(OPERATOR_ROLE_1, OP1_CONTRACT)).toBe(false); }); }); describe('privateState helpers', () => { describe('getCurrentSecretKey', () => { - it('should return the injected secret key', () => { - accessControl.privateState.injectSecretKey(ADMIN.secretKey); + it('should return the injected secret key', async () => { + await accessControl.privateState.injectSecretKey(ADMIN.secretKey); - expect(accessControl.privateState.getCurrentSecretKey()).toEqual( + expect(await accessControl.privateState.getCurrentSecretKey()).toEqual( ADMIN.secretKey, ); }); - it('should throw when the secret key is undefined', () => { - const sim = new AccessControlSimulator({ + it('should throw when the secret key is undefined', async () => { + const sim = await AccessControlSimulator.create({ privateState: { secretKey: undefined as unknown as Uint8Array }, }); - expect(() => sim.privateState.getCurrentSecretKey()).toThrow( + await expect(sim.privateState.getCurrentSecretKey()).rejects.toThrow( 'Missing secret key', ); }); }); - it('should expose an empty public ledger via getPublicState', () => { - expect(accessControl.getPublicState()).toStrictEqual({}); + it('should expose an empty public ledger via getPublicState', async () => { + expect(await accessControl.getPublicState()).toStrictEqual({}); }); }); }); diff --git a/contracts/src/access/test/Ownable.test.ts b/contracts/src/access/test/Ownable.test.ts index 48752163..59eb98b7 100644 --- a/contracts/src/access/test/Ownable.test.ts +++ b/contracts/src/access/test/Ownable.test.ts @@ -75,29 +75,29 @@ const zeroTypes = [ describe('Ownable', () => { describe('before initialized', () => { - it('should initialize', () => { - ownable = new OwnableSimulator(OWNER.either, isInit, { + it('should initialize', async () => { + ownable = await OwnableSimulator.create(OWNER.either, isInit, { privateState: { secretKey: OWNER.secretKey }, }); - expect(ownable.owner()).toEqual(OWNER.either); + expect(await ownable.owner()).toEqual(OWNER.either); }); - it('should fail to initialize when owner is a contract address', () => { - expect(() => { - new OwnableSimulator(OWNER_CONTRACT, isInit, { + it('should fail to initialize when owner is a contract address', async () => { + await expect( + OwnableSimulator.create(OWNER_CONTRACT, isInit, { privateState: { secretKey: OWNER.secretKey }, - }); - }).toThrow('Ownable: unsafe ownership transfer'); + }), + ).rejects.toThrow('Ownable: unsafe ownership transfer'); }); it.each( zeroTypes, - )('should fail to initialize when owner is zero (%s)', (_, _zero) => { - expect(() => { - ownable = new OwnableSimulator(_zero, isInit, { + )('should fail to initialize when owner is zero (%s)', async (_, _zero) => { + await expect( + OwnableSimulator.create(_zero, isInit, { privateState: { secretKey: OWNER.secretKey }, - }); - }).toThrow('Ownable: invalid initial owner'); + }), + ).rejects.toThrow('Ownable: invalid initial owner'); }); type FailingCircuits = [method: keyof OwnableSimulator, args: unknown[]]; @@ -112,27 +112,29 @@ describe('Ownable', () => { ]; it.each( circuitsToFail, - )('should fail when calling circuit "%s"', (circuitName, args) => { - ownable = new OwnableSimulator(OWNER.either, isBadInit, { + )('should fail when calling circuit "%s"', async (circuitName, args) => { + ownable = await OwnableSimulator.create(OWNER.either, isBadInit, { privateState: { secretKey: OWNER.secretKey }, }); - expect(() => { - (ownable[circuitName] as (...args: unknown[]) => unknown)(...args); - }).toThrow('Ownable: contract not initialized'); + await expect( + (ownable[circuitName] as (...args: unknown[]) => Promise)( + ...args, + ), + ).rejects.toThrow('Ownable: contract not initialized'); }); - it('should canonicalize initial owner', () => { + it('should canonicalize initial owner', async () => { const nonCanonical = { is_left: true, left: OWNER.accountId, right: utils.encodeToAddress('JUNK_DATA'), }; - ownable = new OwnableSimulator(nonCanonical, isInit, { + ownable = await OwnableSimulator.create(nonCanonical, isInit, { privateState: { secretKey: OWNER.secretKey }, }); - const stored = ownable.owner(); + const stored = await ownable.owner(); expect(stored.is_left).toBe(true); expect(stored.left).toEqual(OWNER.accountId); expect(stored.right).toEqual({ bytes: zeroBytes }); @@ -140,48 +142,48 @@ describe('Ownable', () => { }); describe('when initialized', () => { - beforeEach(() => { - ownable = new OwnableSimulator(OWNER.either, isInit, { + beforeEach(async () => { + ownable = await OwnableSimulator.create(OWNER.either, isInit, { privateState: { secretKey: OWNER.secretKey }, }); }); describe('owner', () => { - it('should return owner', () => { - expect(ownable.owner()).toEqual(OWNER.either); + it('should return owner', async () => { + expect(await ownable.owner()).toEqual(OWNER.either); }); - it('should return zero when unowned', () => { - ownable._transferOwnership(ZERO_ACCOUNT); - expect(ownable.owner()).toEqual(ZERO_ACCOUNT); + it('should return zero when unowned', async () => { + await ownable._transferOwnership(ZERO_ACCOUNT); + expect(await ownable.owner()).toEqual(ZERO_ACCOUNT); }); }); describe('assertOnlyOwner', () => { - it('should allow owner to call', () => { - expect(() => ownable.assertOnlyOwner()).not.toThrow(); + it('should allow owner to call', async () => { + await expect(ownable.assertOnlyOwner()).resolves.not.toThrow(); }); - it('should fail when called by unauthorized', () => { - ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => ownable.assertOnlyOwner()).toThrow( + it('should fail when called by unauthorized', async () => { + await ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); }); - it('should reject all accountId callers when owner is a contract', () => { - ownable._unsafeTransferOwnership(OWNER_CONTRACT); + it('should reject all accountId callers when owner is a contract', async () => { + await ownable._unsafeTransferOwnership(OWNER_CONTRACT); // Original owner rejected - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: contract address owner authentication is not yet supported', ); // Sample other keys for (const label of ['SAMPLE_1', 'SAMPLE_2', 'SAMPLE_3']) { const sampleUser = makeUser(label); - ownable.privateState.injectSecretKey(sampleUser.secretKey); - expect(() => ownable.assertOnlyOwner()).toThrow( + await ownable.privateState.injectSecretKey(sampleUser.secretKey); + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: contract address owner authentication is not yet supported', ); } @@ -189,165 +191,165 @@ describe('Ownable', () => { }); describe('transferOwnership', () => { - it('should transfer ownership', () => { - ownable.transferOwnership(NEW_OWNER.either); - expect(ownable.owner()).toEqual(NEW_OWNER.either); + it('should transfer ownership', async () => { + await ownable.transferOwnership(NEW_OWNER.either); + expect(await ownable.owner()).toEqual(NEW_OWNER.either); // Original owner can no longer call - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // Unauthorized still can't call - ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => ownable.assertOnlyOwner()).toThrow( + await ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // New owner can call - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - expect(() => ownable.assertOnlyOwner()).not.toThrow(); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await expect(ownable.assertOnlyOwner()).resolves.not.toThrow(); }); - it('should fail when unauthorized transfers ownership', () => { - ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => ownable.transferOwnership(NEW_OWNER.either)).toThrow( - 'Ownable: caller is not the owner', - ); + it('should fail when unauthorized transfers ownership', async () => { + await ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect( + ownable.transferOwnership(NEW_OWNER.either), + ).rejects.toThrow('Ownable: caller is not the owner'); }); - it('should fail when transferring to a contract address', () => { - expect(() => ownable.transferOwnership(RECIPIENT_CONTRACT)).toThrow( - 'Ownable: unsafe ownership transfer', - ); + it('should fail when transferring to a contract address', async () => { + await expect( + ownable.transferOwnership(RECIPIENT_CONTRACT), + ).rejects.toThrow('Ownable: unsafe ownership transfer'); }); - it('should fail when transferring to zero (accountId)', () => { - expect(() => ownable.transferOwnership(ZERO_ACCOUNT)).toThrow( + it('should fail when transferring to zero (accountId)', async () => { + await expect(ownable.transferOwnership(ZERO_ACCOUNT)).rejects.toThrow( 'Ownable: invalid new owner', ); }); - it('should fail when transferring to zero (contract)', () => { - expect(() => ownable.transferOwnership(ZERO_CONTRACT)).toThrow( + it('should fail when transferring to zero (contract)', async () => { + await expect(ownable.transferOwnership(ZERO_CONTRACT)).rejects.toThrow( 'Ownable: unsafe ownership transfer', ); }); - it('should transfer multiple times', () => { - ownable.transferOwnership(NEW_OWNER.either); + it('should transfer multiple times', async () => { + await ownable.transferOwnership(NEW_OWNER.either); - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - ownable.transferOwnership(OWNER.either); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await ownable.transferOwnership(OWNER.either); - ownable.privateState.injectSecretKey(OWNER.secretKey); - ownable.transferOwnership(NEW_OWNER.either); + await ownable.privateState.injectSecretKey(OWNER.secretKey); + await ownable.transferOwnership(NEW_OWNER.either); - expect(ownable.owner()).toEqual(NEW_OWNER.either); + expect(await ownable.owner()).toEqual(NEW_OWNER.either); }); }); describe('_unsafeTransferOwnership', () => { - it('should transfer ownership to accountId', () => { - ownable._unsafeTransferOwnership(NEW_OWNER.either); - expect(ownable.owner()).toEqual(NEW_OWNER.either); + it('should transfer ownership to accountId', async () => { + await ownable._unsafeTransferOwnership(NEW_OWNER.either); + expect(await ownable.owner()).toEqual(NEW_OWNER.either); // Original owner rejected - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // New owner can call - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - expect(() => ownable.assertOnlyOwner()).not.toThrow(); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await expect(ownable.assertOnlyOwner()).resolves.not.toThrow(); }); - it('should transfer ownership to contract', () => { - ownable._unsafeTransferOwnership(OWNER_CONTRACT); - expect(ownable.owner()).toEqual(OWNER_CONTRACT); + it('should transfer ownership to contract', async () => { + await ownable._unsafeTransferOwnership(OWNER_CONTRACT); + expect(await ownable.owner()).toEqual(OWNER_CONTRACT); // No one can authenticate, c2c not supported - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: contract address owner authentication is not yet supported', ); }); - it('should fail when unauthorized transfers ownership', () => { - ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => + it('should fail when unauthorized transfers ownership', async () => { + await ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect( ownable._unsafeTransferOwnership(NEW_OWNER.either), - ).toThrow('Ownable: caller is not the owner'); + ).rejects.toThrow('Ownable: caller is not the owner'); }); - it('should fail when transferring to zero (accountId)', () => { - expect(() => ownable._unsafeTransferOwnership(ZERO_ACCOUNT)).toThrow( - 'Ownable: invalid new owner', - ); + it('should fail when transferring to zero (accountId)', async () => { + await expect( + ownable._unsafeTransferOwnership(ZERO_ACCOUNT), + ).rejects.toThrow('Ownable: invalid new owner'); }); - it('should fail when transferring to zero (contract)', () => { - expect(() => ownable._unsafeTransferOwnership(ZERO_CONTRACT)).toThrow( - 'Ownable: invalid new owner', - ); + it('should fail when transferring to zero (contract)', async () => { + await expect( + ownable._unsafeTransferOwnership(ZERO_CONTRACT), + ).rejects.toThrow('Ownable: invalid new owner'); }); - it('should enforce permissions after transfer (accountId)', () => { - ownable._unsafeTransferOwnership(NEW_OWNER.either); + it('should enforce permissions after transfer (accountId)', async () => { + await ownable._unsafeTransferOwnership(NEW_OWNER.either); // Original owner can no longer call - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // Unauthorized still can't call - ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => ownable.assertOnlyOwner()).toThrow( + await ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // New owner can call - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - expect(() => ownable.assertOnlyOwner()).not.toThrow(); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await expect(ownable.assertOnlyOwner()).resolves.not.toThrow(); }); - it('should transfer multiple times', () => { - ownable._unsafeTransferOwnership(NEW_OWNER.either); + it('should transfer multiple times', async () => { + await ownable._unsafeTransferOwnership(NEW_OWNER.either); - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - ownable._unsafeTransferOwnership(OWNER.either); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await ownable._unsafeTransferOwnership(OWNER.either); - ownable.privateState.injectSecretKey(OWNER.secretKey); - ownable._unsafeTransferOwnership(OWNER_CONTRACT); + await ownable.privateState.injectSecretKey(OWNER.secretKey); + await ownable._unsafeTransferOwnership(OWNER_CONTRACT); - expect(ownable.owner()).toEqual(OWNER_CONTRACT); + expect(await ownable.owner()).toEqual(OWNER_CONTRACT); }); }); describe('renounceOwnership', () => { - it('should renounce ownership', () => { - expect(ownable.owner()).toEqual(OWNER.either); + it('should renounce ownership', async () => { + expect(await ownable.owner()).toEqual(OWNER.either); - ownable.renounceOwnership(); + await ownable.renounceOwnership(); - expect(ownable.owner()).toEqual(ZERO_ACCOUNT); + expect(await ownable.owner()).toEqual(ZERO_ACCOUNT); // Confirm revoked permissions - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); }); - it('should fail when renouncing from unauthorized', () => { - ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => ownable.renounceOwnership()).toThrow( + it('should fail when renouncing from unauthorized', async () => { + await ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect(ownable.renounceOwnership()).rejects.toThrow( 'Ownable: caller is not the owner', ); }); - it('should store canonical zero after renouncing', () => { - ownable.renounceOwnership(); + it('should store canonical zero after renouncing', async () => { + await ownable.renounceOwnership(); - const stored = ownable.owner(); + const stored = await ownable.owner(); expect(stored.is_left).toBe(true); expect(stored.left).toEqual(zeroBytes); expect(stored.right).toEqual({ bytes: zeroBytes }); @@ -355,118 +357,118 @@ describe('Ownable', () => { }); describe('_transferOwnership', () => { - it('should transfer ownership', () => { - ownable._transferOwnership(NEW_OWNER.either); - expect(ownable.owner()).toEqual(NEW_OWNER.either); + it('should transfer ownership', async () => { + await ownable._transferOwnership(NEW_OWNER.either); + expect(await ownable.owner()).toEqual(NEW_OWNER.either); // Original owner can no longer call - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // Unauthorized still can't call - ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => ownable.assertOnlyOwner()).toThrow( + await ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // New owner can call - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - expect(() => ownable.assertOnlyOwner()).not.toThrow(); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await expect(ownable.assertOnlyOwner()).resolves.not.toThrow(); }); - it('should fail when transferring to contract address zero', () => { - expect(() => ownable._transferOwnership(ZERO_CONTRACT)).toThrow( + it('should fail when transferring to contract address zero', async () => { + await expect(ownable._transferOwnership(ZERO_CONTRACT)).rejects.toThrow( 'Ownable: unsafe ownership transfer', ); }); - it('should fail when transferring to non-zero contract address', () => { - expect(() => ownable._transferOwnership(OWNER_CONTRACT)).toThrow( - 'Ownable: unsafe ownership transfer', - ); + it('should fail when transferring to non-zero contract address', async () => { + await expect( + ownable._transferOwnership(OWNER_CONTRACT), + ).rejects.toThrow('Ownable: unsafe ownership transfer'); }); - it('should transfer multiple times', () => { - ownable._transferOwnership(NEW_OWNER.either); + it('should transfer multiple times', async () => { + await ownable._transferOwnership(NEW_OWNER.either); - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - ownable._transferOwnership(OWNER.either); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await ownable._transferOwnership(OWNER.either); - ownable.privateState.injectSecretKey(OWNER.secretKey); - ownable._transferOwnership(NEW_OWNER.either); + await ownable.privateState.injectSecretKey(OWNER.secretKey); + await ownable._transferOwnership(NEW_OWNER.either); - expect(ownable.owner()).toEqual(NEW_OWNER.either); + expect(await ownable.owner()).toEqual(NEW_OWNER.either); }); - it('should allow transfers to zero', () => { - ownable._transferOwnership(ZERO_ACCOUNT); - expect(ownable.owner()).toEqual(ZERO_ACCOUNT); + it('should allow transfers to zero', async () => { + await ownable._transferOwnership(ZERO_ACCOUNT); + expect(await ownable.owner()).toEqual(ZERO_ACCOUNT); // No one can authenticate after zeroing - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); }); }); describe('_unsafeUncheckedTransferOwnership', () => { - it('should transfer ownership to accountId', () => { - ownable._unsafeUncheckedTransferOwnership(NEW_OWNER.either); - expect(ownable.owner()).toEqual(NEW_OWNER.either); + it('should transfer ownership to accountId', async () => { + await ownable._unsafeUncheckedTransferOwnership(NEW_OWNER.either); + expect(await ownable.owner()).toEqual(NEW_OWNER.either); // Original owner rejected - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // New owner can call - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - expect(() => ownable.assertOnlyOwner()).not.toThrow(); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await expect(ownable.assertOnlyOwner()).resolves.not.toThrow(); }); - it('should transfer ownership to contract', () => { - ownable._unsafeUncheckedTransferOwnership(OWNER_CONTRACT); - expect(ownable.owner()).toEqual(OWNER_CONTRACT); + it('should transfer ownership to contract', async () => { + await ownable._unsafeUncheckedTransferOwnership(OWNER_CONTRACT); + expect(await ownable.owner()).toEqual(OWNER_CONTRACT); // No one can authenticate, c2c not supported - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: contract address owner authentication is not yet supported', ); }); - it('should enforce permissions after transfer (accountId)', () => { - ownable._unsafeUncheckedTransferOwnership(NEW_OWNER.either); + it('should enforce permissions after transfer (accountId)', async () => { + await ownable._unsafeUncheckedTransferOwnership(NEW_OWNER.either); // Original owner can no longer call - expect(() => ownable.assertOnlyOwner()).toThrow( + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // Unauthorized still can't call - ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => ownable.assertOnlyOwner()).toThrow( + await ownable.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect(ownable.assertOnlyOwner()).rejects.toThrow( 'Ownable: caller is not the owner', ); // New owner can call - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - expect(() => ownable.assertOnlyOwner()).not.toThrow(); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await expect(ownable.assertOnlyOwner()).resolves.not.toThrow(); }); - it('should transfer multiple times', () => { - ownable._unsafeUncheckedTransferOwnership(NEW_OWNER.either); + it('should transfer multiple times', async () => { + await ownable._unsafeUncheckedTransferOwnership(NEW_OWNER.either); - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - ownable._unsafeUncheckedTransferOwnership(OWNER.either); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await ownable._unsafeUncheckedTransferOwnership(OWNER.either); - ownable.privateState.injectSecretKey(OWNER.secretKey); - ownable._unsafeUncheckedTransferOwnership(OWNER_CONTRACT); + await ownable.privateState.injectSecretKey(OWNER.secretKey); + await ownable._unsafeUncheckedTransferOwnership(OWNER_CONTRACT); - expect(ownable.owner()).toEqual(OWNER_CONTRACT); + expect(await ownable.owner()).toEqual(OWNER_CONTRACT); }); - it('should canonicalize accountId (zero out inactive right side)', () => { + it('should canonicalize accountId (zero out inactive right side)', async () => { // Craft a non-canonical Either: is_left=true but right side has data const nonCanonical = { is_left: true, @@ -474,15 +476,15 @@ describe('Ownable', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - ownable._unsafeUncheckedTransferOwnership(nonCanonical); + await ownable._unsafeUncheckedTransferOwnership(nonCanonical); - const stored = ownable.owner(); + const stored = await ownable.owner(); expect(stored.is_left).toBe(true); expect(stored.left).toEqual(NEW_OWNER.accountId); expect(stored.right).toEqual({ bytes: zeroBytes }); }); - it('should canonicalize contract address (zero out inactive left side)', () => { + it('should canonicalize contract address (zero out inactive left side)', async () => { // Craft a non-canonical Either: is_left=false but left side has data const nonCanonical = { is_left: false, @@ -490,9 +492,9 @@ describe('Ownable', () => { right: utils.encodeToAddress('OWNER_CONTRACT'), }; - ownable._unsafeUncheckedTransferOwnership(nonCanonical); + await ownable._unsafeUncheckedTransferOwnership(nonCanonical); - const stored = ownable.owner(); + const stored = await ownable.owner(); expect(stored.is_left).toBe(false); expect(stored.left).toEqual(zeroBytes); expect(stored.right).toEqual(utils.encodeToAddress('OWNER_CONTRACT')); @@ -501,42 +503,42 @@ describe('Ownable', () => { }); describe('simulator wiring', () => { - it('should construct with a generated private state when none is supplied', () => { - const sim = new OwnableSimulator(OWNER.either, isInit); - const sk = sim.privateState.getCurrentSecretKey(); + it('should construct with a generated private state when none is supplied', async () => { + const sim = await OwnableSimulator.create(OWNER.either, isInit); + const sk = await sim.privateState.getCurrentSecretKey(); expect(sk).toBeInstanceOf(Uint8Array); expect(sk.length).toBe(32); }); - it('should expose an empty public ledger via getPublicState', () => { - const sim = new OwnableSimulator(OWNER.either, isInit, { + it('should expose an empty public ledger via getPublicState', async () => { + const sim = await OwnableSimulator.create(OWNER.either, isInit, { privateState: { secretKey: OWNER.secretKey }, }); - expect(sim.getPublicState()).toStrictEqual({}); + expect(await sim.getPublicState()).toStrictEqual({}); }); }); describe('privateState helpers', () => { describe('getCurrentSecretKey', () => { - it('should return the injected secret key', () => { - ownable = new OwnableSimulator(OWNER.either, isInit, { + it('should return the injected secret key', async () => { + ownable = await OwnableSimulator.create(OWNER.either, isInit, { privateState: { secretKey: OWNER.secretKey }, }); - ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); + await ownable.privateState.injectSecretKey(NEW_OWNER.secretKey); - expect(ownable.privateState.getCurrentSecretKey()).toEqual( + expect(await ownable.privateState.getCurrentSecretKey()).toEqual( NEW_OWNER.secretKey, ); }); - it('should throw when the secret key is undefined', () => { - const sim = new OwnableSimulator(OWNER.either, isInit, { + it('should throw when the secret key is undefined', async () => { + const sim = await OwnableSimulator.create(OWNER.either, isInit, { privateState: { secretKey: undefined as unknown as Uint8Array }, }); - expect(() => sim.privateState.getCurrentSecretKey()).toThrow( + await expect(sim.privateState.getCurrentSecretKey()).rejects.toThrow( 'Missing secret key', ); }); diff --git a/contracts/src/access/test/ShieldedAccessControl.test.ts b/contracts/src/access/test/ShieldedAccessControl.test.ts index d0530e9f..e9488ea3 100644 --- a/contracts/src/access/test/ShieldedAccessControl.test.ts +++ b/contracts/src/access/test/ShieldedAccessControl.test.ts @@ -8,8 +8,8 @@ import { } from '@midnight-ntwrk/compact-runtime'; import { beforeEach, describe, expect, it } from 'vitest'; import type { Ledger } from '../../../artifacts/MockShieldedAccessControl/contract/index.js'; -import { ShieldedAccessControlPrivateState } from './witnesses/ShieldedAccessControlWitnesses.js'; import { ShieldedAccessControlSimulator } from './simulators/ShieldedAccessControlSimulator.js'; +import { ShieldedAccessControlPrivateState } from './witnesses/ShieldedAccessControlWitnesses.js'; const INSTANCE_SALT = new Uint8Array(32).fill(48473095); const COMMITMENT_DOMAIN = 'ShieldedAccessControl:commitment'; @@ -104,8 +104,11 @@ let contract: ShieldedAccessControlSimulator; describe('ShieldedAccessControl', () => { describe('when not initialized', () => { - beforeEach(() => { - contract = new ShieldedAccessControlSimulator(INSTANCE_SALT, false); + beforeEach(async () => { + contract = await ShieldedAccessControlSimulator.create( + INSTANCE_SALT, + false, + ); }); const circuitsRequiringInit: [string, unknown[]][] = [ @@ -119,26 +122,28 @@ describe('ShieldedAccessControl', () => { ['_setRoleAdmin', [ROLE_ADMIN, ROLE_ADMIN]], ]; - it.each(circuitsRequiringInit)('%s should fail', (circuitName, args) => { - expect(() => { + it.each( + circuitsRequiringInit, + )('%s should fail', async (circuitName, args) => { + await expect( ( contract[circuitName as keyof ShieldedAccessControlSimulator] as ( ...a: unknown[] - ) => unknown - )(...args); - }).toThrow('ShieldedAccessControl: contract not initialized'); + ) => Promise + )(...args), + ).rejects.toThrow('ShieldedAccessControl: contract not initialized'); }); - it('_grantRole should independently check initialization', () => { - expect(() => contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: contract not initialized', - ); + it('_grantRole should independently check initialization', async () => { + await expect( + contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: contract not initialized'); }); - it('_revokeRole should independently check initialization', () => { - expect(() => contract._revokeRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: contract not initialized', - ); + it('_revokeRole should independently check initialization', async () => { + await expect( + contract._revokeRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: contract not initialized'); }); const circuitsNotRequiringInit: [string, unknown[]][] = [ @@ -151,85 +156,92 @@ describe('ShieldedAccessControl', () => { it.each( circuitsNotRequiringInit, - )('%s should succeed', (circuitName, args) => { - expect(() => { + )('%s should succeed', async (circuitName, args) => { + await expect( ( contract[circuitName as keyof ShieldedAccessControlSimulator] as ( ...a: unknown[] - ) => unknown - )(...args); - }).not.toThrow(); + ) => Promise + )(...args), + ).resolves.not.toThrow(); }); - it('should fail with zero instanceSalt', () => { - expect(() => { - new ShieldedAccessControlSimulator(new Uint8Array(32), true); - }).toThrow('ShieldedAccessControl: Instance salt must not be 0'); + it('should fail with zero instanceSalt', async () => { + await expect( + ShieldedAccessControlSimulator.create(new Uint8Array(32), true), + ).rejects.toThrow('ShieldedAccessControl: Instance salt must not be 0'); }); }); describe('after initialization', () => { - beforeEach(() => { - contract = new ShieldedAccessControlSimulator(INSTANCE_SALT, true, { - privateState: ShieldedAccessControlPrivateState.withSecretKey(ADMIN_SK), - }); + beforeEach(async () => { + contract = await ShieldedAccessControlSimulator.create( + INSTANCE_SALT, + true, + { + privateState: + ShieldedAccessControlPrivateState.withSecretKey(ADMIN_SK), + }, + ); }); describe('DEFAULT_ADMIN_ROLE', () => { - it('should return zero bytes', () => { - expect(contract.DEFAULT_ADMIN_ROLE()).toStrictEqual(new Uint8Array(32)); + it('should return zero bytes', async () => { + expect(await contract.DEFAULT_ADMIN_ROLE()).toStrictEqual( + new Uint8Array(32), + ); }); }); describe('computeAccountId', () => { - it('should match pre-computed accountId', () => { - expect(contract.computeAccountId(ADMIN_SK, INSTANCE_SALT)).toEqual( - ADMIN_ACCOUNT_ID, - ); + it('should match pre-computed accountId', async () => { + expect( + await contract.computeAccountId(ADMIN_SK, INSTANCE_SALT), + ).toEqual(ADMIN_ACCOUNT_ID); }); - it('should produce different accountId with different key', () => { - expect(contract.computeAccountId(BAD_SK, INSTANCE_SALT)).not.toEqual( - ADMIN_ACCOUNT_ID, - ); + it('should produce different accountId with different key', async () => { + expect( + await contract.computeAccountId(BAD_SK, INSTANCE_SALT), + ).not.toEqual(ADMIN_ACCOUNT_ID); }); - it('should produce different accountId with different salt', () => { + it('should produce different accountId with different salt', async () => { const differentSalt = new Uint8Array(32).fill(1); - expect(contract.computeAccountId(ADMIN_SK, differentSalt)).not.toEqual( - ADMIN_ACCOUNT_ID, - ); + expect( + await contract.computeAccountId(ADMIN_SK, differentSalt), + ).not.toEqual(ADMIN_ACCOUNT_ID); }); - it('should accept zero-byte secret key', () => { + it('should accept zero-byte secret key', async () => { const zeroKey = new Uint8Array(32); - expect(contract.computeAccountId(zeroKey, INSTANCE_SALT)).toEqual( + expect(await contract.computeAccountId(zeroKey, INSTANCE_SALT)).toEqual( buildAccountIdHash(zeroKey), ); }); }); describe('computeRoleCommitment', () => { - it('should match pre-computed commitment', () => { + it('should match pre-computed commitment', async () => { expect( - contract.computeRoleCommitment(ROLE_ADMIN, ADMIN_ACCOUNT_ID), + await contract.computeRoleCommitment(ROLE_ADMIN, ADMIN_ACCOUNT_ID), ).toEqual(ADMIN_ROLE_COMMITMENT); }); - it('should differ with wrong role', () => { + it('should differ with wrong role', async () => { expect( - contract.computeRoleCommitment(ROLE_OP1, ADMIN_ACCOUNT_ID), + await contract.computeRoleCommitment(ROLE_OP1, ADMIN_ACCOUNT_ID), ).not.toEqual(ADMIN_ROLE_COMMITMENT); }); - it('should differ with wrong accountId', () => { + it('should differ with wrong accountId', async () => { expect( - contract.computeRoleCommitment(ROLE_ADMIN, BAD_ACCOUNT_ID), + await contract.computeRoleCommitment(ROLE_ADMIN, BAD_ACCOUNT_ID), ).not.toEqual(ADMIN_ROLE_COMMITMENT); }); - it('should differ with different instanceSalt', () => { - const newContract = new ShieldedAccessControlSimulator( + it('should differ with different instanceSalt', async () => { + const newContract = await ShieldedAccessControlSimulator.create( new Uint8Array(32).fill(1), true, { @@ -238,956 +250,954 @@ describe('ShieldedAccessControl', () => { }, ); expect( - newContract.computeRoleCommitment(ROLE_ADMIN, ADMIN_ACCOUNT_ID), + await newContract.computeRoleCommitment(ROLE_ADMIN, ADMIN_ACCOUNT_ID), ).not.toEqual(ADMIN_ROLE_COMMITMENT); }); }); describe('computeNullifier', () => { - it('should match pre-computed nullifier', () => { - expect(contract.computeNullifier(ADMIN_ROLE_COMMITMENT)).toEqual( + it('should match pre-computed nullifier', async () => { + expect(await contract.computeNullifier(ADMIN_ROLE_COMMITMENT)).toEqual( ADMIN_ROLE_NULLIFIER, ); }); - it('should differ with wrong commitment', () => { - expect(contract.computeNullifier(OP1_ROLE_COMMITMENT)).not.toEqual( - ADMIN_ROLE_NULLIFIER, - ); + it('should differ with wrong commitment', async () => { + expect( + await contract.computeNullifier(OP1_ROLE_COMMITMENT), + ).not.toEqual(ADMIN_ROLE_NULLIFIER); }); }); describe('assertOnlyRole', () => { - beforeEach(() => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + beforeEach(async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); }); describe('should fail', () => { - it('when witness returns path for a different commitment', () => { - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - contract.overrideWitness('wit_getRoleCommitmentPath', () => { - const ps = contract.getPrivateState(); - const path = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( + it('when witness returns path for a different commitment', async () => { + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + contract.overrideWitness('wit_getRoleCommitmentPath', (ctx) => { + const path = + ctx.ledger.ShieldedAccessControl__operatorRoles.findPathForLeaf( OP1_ROLE_COMMITMENT, ); - if (path) return [ps, path]; + if (path) return [ctx.privateState, path]; throw new Error('Path should be defined'); }); - expect(() => contract.assertOnlyRole(ROLE_ADMIN)).toThrow( + await expect(contract.assertOnlyRole(ROLE_ADMIN)).rejects.toThrow( 'ShieldedAccessControl: Path must contain leaf matching computed role commitment for the provided role, accountId pairing', ); }); - it('when caller has wrong secret key', () => { - contract.privateState.injectSecretKey(UNAUTHORIZED_SK); - expect(() => contract.assertOnlyRole(ROLE_ADMIN)).toThrow( + it('when caller has wrong secret key', async () => { + await contract.privateState.injectSecretKey(UNAUTHORIZED_SK); + await expect(contract.assertOnlyRole(ROLE_ADMIN)).rejects.toThrow( 'ShieldedAccessControl: unauthorized account', ); }); - it('when witness provides invalid path', () => { + it('when witness provides invalid path', async () => { contract.overrideWitness( 'wit_getRoleCommitmentPath', RETURN_BAD_PATH, ); - expect(() => contract.assertOnlyRole(ROLE_ADMIN)).toThrow( + await expect(contract.assertOnlyRole(ROLE_ADMIN)).rejects.toThrow( 'ShieldedAccessControl: unauthorized account', ); }); - it('when role is revoked', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(() => contract.assertOnlyRole(ROLE_ADMIN)).toThrow( + it('when role is revoked', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await expect(contract.assertOnlyRole(ROLE_ADMIN)).rejects.toThrow( 'ShieldedAccessControl: unauthorized account', ); }); - it('when role was never granted to anyone', () => { - expect(() => contract.assertOnlyRole(ROLE_NONEXISTENT)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + it('when role was never granted to anyone', async () => { + await expect( + contract.assertOnlyRole(ROLE_NONEXISTENT), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); }); describe('should succeed', () => { - it('when caller has correct key and valid path', () => { - expect(() => contract.assertOnlyRole(ROLE_ADMIN)).not.toThrow(); + it('when caller has correct key and valid path', async () => { + await expect( + contract.assertOnlyRole(ROLE_ADMIN), + ).resolves.not.toThrow(); }); - it('when caller holds multiple roles with same key', () => { - contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP3, ADMIN_ACCOUNT_ID); - - expect(() => { - contract.assertOnlyRole(ROLE_ADMIN); - contract.assertOnlyRole(ROLE_OP1); - contract.assertOnlyRole(ROLE_OP2); - contract.assertOnlyRole(ROLE_OP3); - }).not.toThrow(); + it('when caller holds multiple roles with same key', async () => { + await contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP3, ADMIN_ACCOUNT_ID); + + await contract.assertOnlyRole(ROLE_ADMIN); + await contract.assertOnlyRole(ROLE_OP1); + await contract.assertOnlyRole(ROLE_OP2); + await contract.assertOnlyRole(ROLE_OP3); }); - it('when role is revoked and re-issued with new accountId', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('when role is revoked and re-issued with new accountId', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); const newKey = Buffer.alloc(32, 'NEW_ADMIN_KEY'); - contract.privateState.injectSecretKey(newKey); + await contract.privateState.injectSecretKey(newKey); const newAccountId = buildAccountIdHash(newKey); - contract._grantRole(ROLE_ADMIN, newAccountId); + await contract._grantRole(ROLE_ADMIN, newAccountId); - expect(() => contract.assertOnlyRole(ROLE_ADMIN)).not.toThrow(); + await expect( + contract.assertOnlyRole(ROLE_ADMIN), + ).resolves.not.toThrow(); }); }); }); describe('canProveRole', () => { - beforeEach(() => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + beforeEach(async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); }); - it('should fail when witness returns path for a different commitment', () => { - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - contract.overrideWitness('wit_getRoleCommitmentPath', () => { - const ps = contract.getPrivateState(); - const path = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( + it('should fail when witness returns path for a different commitment', async () => { + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + contract.overrideWitness('wit_getRoleCommitmentPath', (ctx) => { + const path = + ctx.ledger.ShieldedAccessControl__operatorRoles.findPathForLeaf( OP1_ROLE_COMMITMENT, ); - if (path) return [ps, path]; + if (path) return [ctx.privateState, path]; throw new Error('Path should be defined'); }); - expect(() => contract.canProveRole(ROLE_ADMIN)).toThrow( + await expect(contract.canProveRole(ROLE_ADMIN)).rejects.toThrow( 'ShieldedAccessControl: Path must contain leaf matching computed role commitment for the provided role, accountId pairing', ); }); describe('should return true', () => { - it('when caller has role', () => { - expect(contract.canProveRole(ROLE_ADMIN)).toBe(true); + it('when caller has role', async () => { + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(true); }); - it('when caller holds multiple roles with same key', () => { - contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP3, ADMIN_ACCOUNT_ID); + it('when caller holds multiple roles with same key', async () => { + await contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP3, ADMIN_ACCOUNT_ID); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(true); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); - expect(contract.canProveRole(ROLE_OP2)).toBe(true); - expect(contract.canProveRole(ROLE_OP3)).toBe(true); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(true); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); + expect(await contract.canProveRole(ROLE_OP2)).toBe(true); + expect(await contract.canProveRole(ROLE_OP3)).toBe(true); }); - it('when role is revoked and re-issued with new accountId', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('when role is revoked and re-issued with new accountId', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); const newKey = Buffer.alloc(32, 'NEW_ADMIN_KEY'); - contract.privateState.injectSecretKey(newKey); + await contract.privateState.injectSecretKey(newKey); const newAccountId = buildAccountIdHash(newKey); - contract._grantRole(ROLE_ADMIN, newAccountId); + await contract._grantRole(ROLE_ADMIN, newAccountId); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(true); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(true); }); - it('when multiple users hold the same role', () => { - contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); + it('when multiple users hold the same role', async () => { + await contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); // User 2 - contract._grantRole(ROLE_OP1, OP2_ACCOUNT_ID); + await contract._grantRole(ROLE_OP1, OP2_ACCOUNT_ID); // User 3 - contract._grantRole(ROLE_OP1, OP3_ACCOUNT_ID); + await contract._grantRole(ROLE_OP1, OP3_ACCOUNT_ID); // Prove as admin (who holds OP1) - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); // Prove as user 2 - contract.privateState.injectSecretKey(OPERATOR_2_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + await contract.privateState.injectSecretKey(OPERATOR_2_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); // Prove as user 3 - contract.privateState.injectSecretKey(OPERATOR_3_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + await contract.privateState.injectSecretKey(OPERATOR_3_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); }); }); describe('should return false', () => { - it('when caller does not have role', () => { - expect(contract.canProveRole(ROLE_OP1)).toBe(false); + it('when caller does not have role', async () => { + expect(await contract.canProveRole(ROLE_OP1)).toBe(false); }); - it('when caller has wrong secret key', () => { - contract.privateState.injectSecretKey(BAD_SK); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(false); + it('when caller has wrong secret key', async () => { + await contract.privateState.injectSecretKey(BAD_SK); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(false); }); - it('when role is revoked', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(false); + it('when role is revoked', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(false); }); - it('when witness provides invalid path', () => { + it('when witness provides invalid path', async () => { contract.overrideWitness( 'wit_getRoleCommitmentPath', RETURN_BAD_PATH, ); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(false); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(false); }); - it('when invalid witness path is provided for a revoked role', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('when invalid witness path is provided for a revoked role', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); contract.overrideWitness( 'wit_getRoleCommitmentPath', RETURN_BAD_PATH, ); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(false); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(false); }); }); }); describe('grantRole', () => { - beforeEach(() => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + beforeEach(async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); }); describe('should fail', () => { - it('when caller does not have admin role', () => { - contract.privateState.injectSecretKey(UNAUTHORIZED_SK); - expect(() => contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + it('when caller does not have admin role', async () => { + await contract.privateState.injectSecretKey(UNAUTHORIZED_SK); + await expect( + contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); - it('when granting to an already-revoked accountId', () => { - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - contract._revokeRole(ROLE_OP1, OP1_ACCOUNT_ID); + it('when granting to an already-revoked accountId', async () => { + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + await contract._revokeRole(ROLE_OP1, OP1_ACCOUNT_ID); - expect(() => contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: role is already revoked', - ); + await expect( + contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); }); - it('when admin provides wrong secret key', () => { - contract.privateState.injectSecretKey(BAD_SK); - expect(() => contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + it('when admin provides wrong secret key', async () => { + await contract.privateState.injectSecretKey(BAD_SK); + await expect( + contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); - it('when admin provides invalid witness path', () => { + it('when admin provides invalid witness path', async () => { contract.overrideWitness( 'wit_getRoleCommitmentPath', RETURN_BAD_PATH, ); - expect(() => contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + await expect( + contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); - it('when admin role has been reassigned via _setRoleAdmin', () => { - contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); + it('when admin role has been reassigned via _setRoleAdmin', async () => { + await contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); // ADMIN holds DEFAULT_ADMIN_ROLE but not ROLE_OP1 - expect(() => contract.grantRole(ROLE_OP2, OP2_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + await expect( + contract.grantRole(ROLE_OP2, OP2_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); - it('when witness returns path for a different commitment', () => { - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - contract.overrideWitness('wit_getRoleCommitmentPath', () => { - const ps = contract.getPrivateState(); - const path = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( + it('when witness returns path for a different commitment', async () => { + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + contract.overrideWitness('wit_getRoleCommitmentPath', (ctx) => { + const path = + ctx.ledger.ShieldedAccessControl__operatorRoles.findPathForLeaf( OP1_ROLE_COMMITMENT, ); - if (path) return [ps, path]; + if (path) return [ctx.privateState, path]; throw new Error('Path should be defined'); }); - expect(() => + await expect( contract.grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), - ).toThrow( + ).rejects.toThrow( 'ShieldedAccessControl: Path must contain leaf matching computed role commitment for the provided role, accountId pairing', ); }); - it('when admin with duplicate grants is revoked', () => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); // duplicate - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('when admin with duplicate grants is revoked', async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); // duplicate + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(() => contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + await expect( + contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); }); describe('should succeed', () => { - it('when caller has admin role', () => { - expect(() => + it('when caller has admin role', async () => { + await expect( contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); }); - it('when granting the same role multiple times to the same accountId', () => { - expect(() => + it('when granting the same role multiple times to the same accountId', async () => { + await expect( contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); - expect(() => + ).resolves.not.toThrow(); + await expect( contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); - expect(() => + ).resolves.not.toThrow(); + await expect( contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); + ).resolves.not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); }); - it('when caller has custom admin role', () => { - contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); - contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + it('when caller has custom admin role', async () => { + await contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); + await contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID); // Switch to operator 1 - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(() => + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + await expect( contract.grantRole(ROLE_OP2, OP2_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_2_SK); - expect(contract.canProveRole(ROLE_OP2)).toBe(true); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_2_SK); + expect(await contract.canProveRole(ROLE_OP2)).toBe(true); }); - it('when admin role is revoked and re-issued with new accountId', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('when admin role is revoked and re-issued with new accountId', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); const newKey = Buffer.alloc(32, 'NEW_ADMIN_KEY'); - contract.privateState.injectSecretKey(newKey); + await contract.privateState.injectSecretKey(newKey); const newAccountId = buildAccountIdHash(newKey); - contract._grantRole(ROLE_ADMIN, newAccountId); + await contract._grantRole(ROLE_ADMIN, newAccountId); - expect(() => + await expect( contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); }); - it('when multiple admins exist', () => { - contract._grantRole(ROLE_ADMIN, OP1_ACCOUNT_ID); - contract._grantRole(ROLE_ADMIN, OP2_ACCOUNT_ID); + it('when multiple admins exist', async () => { + await contract._grantRole(ROLE_ADMIN, OP1_ACCOUNT_ID); + await contract._grantRole(ROLE_ADMIN, OP2_ACCOUNT_ID); // Admin 1 can grant - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(() => + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + await expect( contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); + ).resolves.not.toThrow(); // Admin 2 can grant - contract.privateState.injectSecretKey(OPERATOR_2_SK); - expect(() => + await contract.privateState.injectSecretKey(OPERATOR_2_SK); + await expect( contract.grantRole(ROLE_OP2, OP2_ACCOUNT_ID), - ).not.toThrow(); + ).resolves.not.toThrow(); }); - it('when admin holds multiple roles', () => { - contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); + it('when admin holds multiple roles', async () => { + await contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); - expect(() => + await expect( contract.grantRole(ROLE_OP3, OP3_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_3_SK); - expect(contract.canProveRole(ROLE_OP3)).toBe(true); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_3_SK); + expect(await contract.canProveRole(ROLE_OP3)).toBe(true); }); - it('when re-granting an active role (duplicate)', () => { - expect(() => + it('when re-granting an active role (duplicate)', async () => { + await expect( contract.grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), - ).not.toThrow(); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(true); + ).resolves.not.toThrow(); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(true); }); }); }); describe('_grantRole', () => { - it('should insert commitment into Merkle tree', () => { - let root = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); + it('should insert commitment into Merkle tree', async () => { + let root = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); expect(root.field).toBe(0n); - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - root = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); + root = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); expect(root.field).not.toBe(0n); - const path = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( - ADMIN_ROLE_COMMITMENT, - ); + const path = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.findPathForLeaf( + ADMIN_ROLE_COMMITMENT, + ); expect(path).toBeDefined(); expect(path?.leaf).toStrictEqual(ADMIN_ROLE_COMMITMENT); }); - it('should insert multiple commitments into Merkle tree', () => { - const root = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); + it('should insert multiple commitments into Merkle tree', async () => { + const root = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); expect(root.field).toBe(0n); - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - const root1 = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + const root1 = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); expect(root1.field).not.toBe(root.field); - contract._grantRole(ROLE_ADMIN, OP1_ACCOUNT_ID); - const root2 = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); + await contract._grantRole(ROLE_ADMIN, OP1_ACCOUNT_ID); + const root2 = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); expect(root2.field).not.toBe(root.field); expect(root2.field).not.toBe(root1.field); }); - it('should insert multiple leaves for the same (role, accountId)', () => { - const rootBefore = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); + it('should insert multiple leaves for the same (role, accountId)', async () => { + const rootBefore = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - const rootAfterFirst = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + const rootAfterFirst = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - const rootAfterSecond = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + const rootAfterSecond = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); // Each grant should change the root (new leaf inserted) expect(rootAfterFirst).not.toEqual(rootBefore); expect(rootAfterSecond).not.toEqual(rootAfterFirst); }); - it('should invalidate all duplicates with a single revocation', () => { - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + it('should invalidate all duplicates with a single revocation', async () => { + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); - contract._revokeRole(ROLE_OP1, OP1_ACCOUNT_ID); + await contract._revokeRole(ROLE_OP1, OP1_ACCOUNT_ID); - expect(contract.canProveRole(ROLE_OP1)).toBe(false); + expect(await contract.canProveRole(ROLE_OP1)).toBe(false); }); - it('should throw when granting to a revoked accountId', () => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('should throw when granting to a revoked accountId', async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(() => contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: role is already revoked', - ); + await expect( + contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); }); - it('should not update tree when granting to a revoked accountId', () => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('should not update tree when granting to a revoked accountId', async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - const rootBefore = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); - expect(() => contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: role is already revoked', - ); - const rootAfter = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.root(); + const rootBefore = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); + await expect( + contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); + const rootAfter = ( + await contract.getPublicState() + ).ShieldedAccessControl__operatorRoles.root(); expect(rootBefore).toEqual(rootAfter); }); - it('should allow granting same role to new accountId after revoking different accountId', () => { - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - contract._revokeRole(ROLE_OP1, OP1_ACCOUNT_ID); + it('should allow granting same role to new accountId after revoking different accountId', async () => { + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + await contract._revokeRole(ROLE_OP1, OP1_ACCOUNT_ID); // Different accountId for the same role - expect(() => + await expect( contract._grantRole(ROLE_OP1, OP2_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_2_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_2_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); }); }); describe('revokeRole', () => { - beforeEach(() => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + beforeEach(async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); }); describe('should fail', () => { - it('when caller does not have admin role', () => { - contract.privateState.injectSecretKey(UNAUTHORIZED_SK); - expect(() => contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + it('when caller does not have admin role', async () => { + await contract.privateState.injectSecretKey(UNAUTHORIZED_SK); + await expect( + contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); - it('when re-revoking an already revoked role', () => { - contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID); - expect(() => contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: role is already revoked', - ); + it('when re-revoking an already revoked role', async () => { + await contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID); + await expect( + contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); }); - it('when admin provides wrong secret key', () => { - contract.privateState.injectSecretKey(BAD_SK); - expect(() => contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + it('when admin provides wrong secret key', async () => { + await contract.privateState.injectSecretKey(BAD_SK); + await expect( + contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); - it('when admin provides invalid witness path', () => { + it('when admin provides invalid witness path', async () => { contract.overrideWitness( 'wit_getRoleCommitmentPath', RETURN_BAD_PATH, ); - expect(() => contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + await expect( + contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); - it('when witness returns path for a different commitment', () => { - contract.overrideWitness('wit_getRoleCommitmentPath', () => { - const ps = contract.getPrivateState(); - const path = contract - .getPublicState() - .ShieldedAccessControl__operatorRoles.findPathForLeaf( + it('when witness returns path for a different commitment', async () => { + contract.overrideWitness('wit_getRoleCommitmentPath', (ctx) => { + const path = + ctx.ledger.ShieldedAccessControl__operatorRoles.findPathForLeaf( OP1_ROLE_COMMITMENT, ); - if (path) return [ps, path]; + if (path) return [ctx.privateState, path]; throw new Error('Path should be defined'); }); - expect(() => + await expect( contract.revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), - ).toThrow( + ).rejects.toThrow( 'ShieldedAccessControl: Path must contain leaf matching computed role commitment for the provided role, accountId pairing', ); }); }); describe('should succeed', () => { - it('when caller has admin role', () => { - expect(() => + it('when caller has admin role', async () => { + await expect( contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(false); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(false); }); - it('when caller has custom admin role', () => { - contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); - contract._grantRole(ROLE_OP2, OP2_ACCOUNT_ID); + it('when caller has custom admin role', async () => { + await contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); + await contract._grantRole(ROLE_OP2, OP2_ACCOUNT_ID); - contract.privateState.injectSecretKey(OPERATOR_1_SK); - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - expect(() => + await expect( contract.revokeRole(ROLE_OP2, OP2_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_2_SK); - expect(contract.canProveRole(ROLE_OP2)).toBe(false); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_2_SK); + expect(await contract.canProveRole(ROLE_OP2)).toBe(false); }); - it('when admin self-revokes then cannot further grant or revoke', () => { - contract.revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('when admin self-revokes then cannot further grant or revoke', async () => { + await contract.revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(() => contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); - expect(() => contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + await expect( + contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); + await expect( + contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); }); - it('when revoking a role that was never granted', () => { - expect(() => + it('when revoking a role that was never granted', async () => { + await expect( contract.revokeRole(ROLE_NONEXISTENT, ADMIN_ACCOUNT_ID), - ).not.toThrow(); - expect(contract.canProveRole(ROLE_NONEXISTENT)).toBe(false); + ).resolves.not.toThrow(); + expect(await contract.canProveRole(ROLE_NONEXISTENT)).toBe(false); }); - it('when revoking a role from an unauthorized accountId that was never granted', () => { - expect(() => + it('when revoking a role from an unauthorized accountId that was never granted', async () => { + await expect( contract.revokeRole(ROLE_OP1, UNAUTHORIZED_ACCOUNT_ID), - ).not.toThrow(); + ).resolves.not.toThrow(); - expect(() => + await expect( contract._grantRole(ROLE_OP1, UNAUTHORIZED_ACCOUNT_ID), - ).toThrow('ShieldedAccessControl: role is already revoked'); + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); }); - it('when revoking a never-granted role should permanently block future grants', () => { - contract.revokeRole(ROLE_NONEXISTENT, OP2_ACCOUNT_ID); + it('when revoking a never-granted role should permanently block future grants', async () => { + await contract.revokeRole(ROLE_NONEXISTENT, OP2_ACCOUNT_ID); - expect(() => + await expect( contract._grantRole(ROLE_NONEXISTENT, OP2_ACCOUNT_ID), - ).toThrow('ShieldedAccessControl: role is already revoked'); + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); }); - it('when admin role is revoked and re-issued then can revoke again', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('when admin role is revoked and re-issued then can revoke again', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); const newKey = Buffer.alloc(32, 'NEW_ADMIN_KEY'); - contract.privateState.injectSecretKey(newKey); + await contract.privateState.injectSecretKey(newKey); const newAccountId = buildAccountIdHash(newKey); - contract._grantRole(ROLE_ADMIN, newAccountId); + await contract._grantRole(ROLE_ADMIN, newAccountId); - expect(() => + await expect( contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(false); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(false); }); }); }); describe('_revokeRole', () => { - beforeEach(() => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + beforeEach(async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); }); - it('should insert nullifier into set', () => { + it('should insert nullifier into set', async () => { expect( - contract - .getPublicState() - .ShieldedAccessControl__roleCommitmentNullifiers.size(), + ( + await contract.getPublicState() + ).ShieldedAccessControl__roleCommitmentNullifiers.size(), ).toBe(0n); - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); expect( - contract - .getPublicState() - .ShieldedAccessControl__roleCommitmentNullifiers.size(), + ( + await contract.getPublicState() + ).ShieldedAccessControl__roleCommitmentNullifiers.size(), ).toBe(1n); expect( - contract - .getPublicState() - .ShieldedAccessControl__roleCommitmentNullifiers.member( - ADMIN_ROLE_NULLIFIER, - ), + ( + await contract.getPublicState() + ).ShieldedAccessControl__roleCommitmentNullifiers.member( + ADMIN_ROLE_NULLIFIER, + ), ).toBe(true); }); - it('should throw when re-revoking', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(() => + it('should throw when re-revoking', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await expect( contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), - ).toThrow('ShieldedAccessControl: role is already revoked'); + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); }); - it('should not update nullifier set when re-revoking', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - const sizeBefore = contract - .getPublicState() - .ShieldedAccessControl__roleCommitmentNullifiers.size(); + it('should not update nullifier set when re-revoking', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + const sizeBefore = ( + await contract.getPublicState() + ).ShieldedAccessControl__roleCommitmentNullifiers.size(); - expect(() => + await expect( contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), - ).toThrow(); - const sizeAfter = contract - .getPublicState() - .ShieldedAccessControl__roleCommitmentNullifiers.size(); + ).rejects.toThrow(); + const sizeAfter = ( + await contract.getPublicState() + ).ShieldedAccessControl__roleCommitmentNullifiers.size(); expect(sizeBefore).toEqual(sizeAfter); }); - it('should allow revoking a role that was never granted', () => { - expect(() => + it('should allow revoking a role that was never granted', async () => { + await expect( contract._revokeRole(ROLE_NONEXISTENT, ADMIN_ACCOUNT_ID), - ).not.toThrow(); + ).resolves.not.toThrow(); }); }); describe('renounceRole', () => { - beforeEach(() => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + beforeEach(async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); }); - it('should allow caller to renounce their own role', () => { - expect(() => + it('should allow caller to renounce their own role', async () => { + await expect( contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), - ).not.toThrow(); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(false); + ).resolves.not.toThrow(); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(false); }); - it('should update nullifier set', () => { + it('should update nullifier set', async () => { expect( - contract - .getPublicState() - .ShieldedAccessControl__roleCommitmentNullifiers.size(), + ( + await contract.getPublicState() + ).ShieldedAccessControl__roleCommitmentNullifiers.size(), ).toBe(0n); - contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); expect( - contract - .getPublicState() - .ShieldedAccessControl__roleCommitmentNullifiers.size(), + ( + await contract.getPublicState() + ).ShieldedAccessControl__roleCommitmentNullifiers.size(), ).toBe(1n); expect( - contract - .getPublicState() - .ShieldedAccessControl__roleCommitmentNullifiers.member( - ADMIN_ROLE_NULLIFIER, - ), + ( + await contract.getPublicState() + ).ShieldedAccessControl__roleCommitmentNullifiers.member( + ADMIN_ROLE_NULLIFIER, + ), ).toBe(true); }); - it('should fail when caller provides wrong accountId', () => { - expect(() => contract.renounceRole(ROLE_ADMIN, BAD_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: bad confirmation', - ); + it('should fail when caller provides wrong accountId', async () => { + await expect( + contract.renounceRole(ROLE_ADMIN, BAD_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: bad confirmation'); }); - it('should fail when caller has wrong secret key', () => { - contract.privateState.injectSecretKey(UNAUTHORIZED_SK); - expect(() => + it('should fail when caller has wrong secret key', async () => { + await contract.privateState.injectSecretKey(UNAUTHORIZED_SK); + await expect( contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), - ).toThrow('ShieldedAccessControl: bad confirmation'); + ).rejects.toThrow('ShieldedAccessControl: bad confirmation'); }); - it('should throw when role is already revoked', () => { - contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(() => + it('should throw when role is already revoked', async () => { + await contract._revokeRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await expect( contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), - ).toThrow('ShieldedAccessControl: role is already revoked'); + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); }); - it('should permanently block re-grant to same accountId', () => { - contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(() => contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: role is already revoked', - ); + it('should permanently block re-grant to same accountId', async () => { + await contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await expect( + contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); }); - it('should allow re-grant with new accountId after renounce', () => { - contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('should allow re-grant with new accountId after renounce', async () => { + await contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); const newKey = Buffer.alloc(32, 'NEW_ADMIN_KEY'); - contract.privateState.injectSecretKey(newKey); + await contract.privateState.injectSecretKey(newKey); const newAccountId = buildAccountIdHash(newKey); - contract._grantRole(ROLE_ADMIN, newAccountId); + await contract._grantRole(ROLE_ADMIN, newAccountId); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(true); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(true); }); - it('should not affect other roles held by same accountId', () => { - contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); + it('should not affect other roles held by same accountId', async () => { + await contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); - contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await contract.renounceRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(false); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); - expect(contract.canProveRole(ROLE_OP2)).toBe(true); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(false); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); + expect(await contract.canProveRole(ROLE_OP2)).toBe(true); }); // Pre-burn scenario: a user can burn a nullifier for a (role, accountId) pairing // that was never granted. This permanently blocks future grants to that accountId // for the specified role, but does not affect other accountIds holding the same role - it('should allow renouncing a role never granted to this accountId', () => { + it('should allow renouncing a role never granted to this accountId', async () => { // OP1 has ROLE_OP1, but ADMIN does not - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); // ADMIN renounces ROLE_OP1 despite never holding it - expect(() => + await expect( contract.renounceRole(ROLE_OP1, ADMIN_ACCOUNT_ID), - ).not.toThrow(); + ).resolves.not.toThrow(); // OP1's grant is unaffected — different accountId, different nullifier - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); // ADMIN's accountId is now burned for ROLE_OP1 - expect(() => contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: role is already revoked', - ); + await expect( + contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: role is already revoked'); }); }); describe('getRoleAdmin', () => { - it('should return DEFAULT_ADMIN_ROLE when no admin set', () => { - expect(contract.getRoleAdmin(ROLE_OP1)).toStrictEqual( + it('should return DEFAULT_ADMIN_ROLE when no admin set', async () => { + expect(await contract.getRoleAdmin(ROLE_OP1)).toStrictEqual( new Uint8Array(32), ); - expect(contract.getRoleAdmin(ROLE_OP1)).toStrictEqual( - contract.DEFAULT_ADMIN_ROLE(), + expect(await contract.getRoleAdmin(ROLE_OP1)).toStrictEqual( + await contract.DEFAULT_ADMIN_ROLE(), ); }); - it('should restore DEFAULT_ADMIN_ROLE grant/revoke authority after reset to zero bytes', () => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + it('should restore DEFAULT_ADMIN_ROLE grant/revoke authority after reset to zero bytes', async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); // Reassign OP1's admin to OP2 - contract._setRoleAdmin(ROLE_OP1, ROLE_OP2); + await contract._setRoleAdmin(ROLE_OP1, ROLE_OP2); // DEFAULT_ADMIN_ROLE holder cannot grant ROLE_OP1 anymore - expect(() => contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + await expect( + contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); // Reset OP1's admin back to DEFAULT_ADMIN_ROLE - contract._setRoleAdmin(ROLE_OP1, new Uint8Array(32)); + await contract._setRoleAdmin(ROLE_OP1, new Uint8Array(32)); // DEFAULT_ADMIN_ROLE holder can grant ROLE_OP1 again - expect(() => + await expect( contract.grantRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); // And can revoke - contract.privateState.injectSecretKey(ADMIN_SK); - expect(() => + await contract.privateState.injectSecretKey(ADMIN_SK); + await expect( contract.revokeRole(ROLE_OP1, OP1_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(contract.canProveRole(ROLE_OP1)).toBe(false); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + expect(await contract.canProveRole(ROLE_OP1)).toBe(false); }); - it('should return admin role after _setRoleAdmin', () => { - contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); - expect(contract.getRoleAdmin(ROLE_OP1)).toEqual( + it('should return admin role after _setRoleAdmin', async () => { + await contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); + expect(await contract.getRoleAdmin(ROLE_OP1)).toEqual( new Uint8Array(ROLE_ADMIN), ); }); }); describe('_setRoleAdmin', () => { - it('should set admin role', () => { - contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); - expect(contract.getRoleAdmin(ROLE_OP1)).toEqual( + it('should set admin role', async () => { + await contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); + expect(await contract.getRoleAdmin(ROLE_OP1)).toEqual( new Uint8Array(ROLE_ADMIN), ); }); - it('should update _adminRoles map', () => { + it('should update _adminRoles map', async () => { expect( - contract.getPublicState().ShieldedAccessControl__adminRoles.isEmpty(), + ( + await contract.getPublicState() + ).ShieldedAccessControl__adminRoles.isEmpty(), ).toBe(true); - contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); - contract._setRoleAdmin(ROLE_OP2, ROLE_ADMIN); - contract._setRoleAdmin(ROLE_OP3, ROLE_ADMIN); + await contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); + await contract._setRoleAdmin(ROLE_OP2, ROLE_ADMIN); + await contract._setRoleAdmin(ROLE_OP3, ROLE_ADMIN); expect( - contract.getPublicState().ShieldedAccessControl__adminRoles.size(), + ( + await contract.getPublicState() + ).ShieldedAccessControl__adminRoles.size(), ).toBe(3n); }); - it('should override existing admin role', () => { - contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); - contract._setRoleAdmin(ROLE_OP1, ROLE_OP2); - expect(contract.getRoleAdmin(ROLE_OP1)).toEqual( + it('should override existing admin role', async () => { + await contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); + await contract._setRoleAdmin(ROLE_OP1, ROLE_OP2); + expect(await contract.getRoleAdmin(ROLE_OP1)).toEqual( new Uint8Array(ROLE_OP2), ); }); - it('should return DEFAULT_ADMIN_ROLE when reset to zero bytes', () => { - contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); - contract._setRoleAdmin(ROLE_OP1, new Uint8Array(32)); - expect(contract.getRoleAdmin(ROLE_OP1)).toStrictEqual( - contract.DEFAULT_ADMIN_ROLE(), + it('should return DEFAULT_ADMIN_ROLE when reset to zero bytes', async () => { + await contract._setRoleAdmin(ROLE_OP1, ROLE_ADMIN); + await contract._setRoleAdmin(ROLE_OP1, new Uint8Array(32)); + expect(await contract.getRoleAdmin(ROLE_OP1)).toStrictEqual( + await contract.DEFAULT_ADMIN_ROLE(), ); }); - it('should allow a role to be its own admin', () => { - contract._setRoleAdmin(ROLE_OP1, ROLE_OP1); - expect(contract.getRoleAdmin(ROLE_OP1)).toEqual( + it('should allow a role to be its own admin', async () => { + await contract._setRoleAdmin(ROLE_OP1, ROLE_OP1); + expect(await contract.getRoleAdmin(ROLE_OP1)).toEqual( new Uint8Array(ROLE_OP1), ); }); - it('when new admin revokes after _setRoleAdmin reassignment', () => { - contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - contract._grantRole(ROLE_OP2, OP2_ACCOUNT_ID); + it('when new admin revokes after _setRoleAdmin reassignment', async () => { + await contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + await contract._grantRole(ROLE_OP2, OP2_ACCOUNT_ID); // Switch to operator 1 who is now admin of ROLE_OP2 - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(() => + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + await expect( contract.revokeRole(ROLE_OP2, OP2_ACCOUNT_ID), - ).not.toThrow(); - contract.privateState.injectSecretKey(OPERATOR_2_SK); - expect(contract.canProveRole(ROLE_OP2)).toBe(false); + ).resolves.not.toThrow(); + await contract.privateState.injectSecretKey(OPERATOR_2_SK); + expect(await contract.canProveRole(ROLE_OP2)).toBe(false); }); - it('admin authority should not be transitive across role hierarchies', () => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + it('admin authority should not be transitive across role hierarchies', async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await contract._setRoleAdmin(ROLE_OP2, ROLE_OP1); + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); // ADMIN can grant ROLE_OP1 (admin is DEFAULT_ADMIN_ROLE) - expect(() => + await expect( contract.grantRole(ROLE_OP1, OP2_ACCOUNT_ID), - ).not.toThrow(); + ).resolves.not.toThrow(); // But ADMIN cannot directly grant ROLE_OP2 (admin is ROLE_OP1, not DEFAULT_ADMIN_ROLE) - expect(() => contract.grantRole(ROLE_OP2, OP3_ACCOUNT_ID)).toThrow( - 'ShieldedAccessControl: unauthorized account', - ); + await expect( + contract.grantRole(ROLE_OP2, OP3_ACCOUNT_ID), + ).rejects.toThrow('ShieldedAccessControl: unauthorized account'); // OP1 holder can grant ROLE_OP2 - contract.privateState.injectSecretKey(OPERATOR_1_SK); - expect(() => + await contract.privateState.injectSecretKey(OPERATOR_1_SK); + await expect( contract.grantRole(ROLE_OP2, OP3_ACCOUNT_ID), - ).not.toThrow(); + ).resolves.not.toThrow(); }); }); describe('single key across multiple roles', () => { - beforeEach(() => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); - contract._grantRole(ROLE_OP3, ADMIN_ACCOUNT_ID); + beforeEach(async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP1, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP2, ADMIN_ACCOUNT_ID); + await contract._grantRole(ROLE_OP3, ADMIN_ACCOUNT_ID); }); - it('should prove all roles with same key', () => { - expect(contract.canProveRole(ROLE_ADMIN)).toBe(true); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); - expect(contract.canProveRole(ROLE_OP2)).toBe(true); - expect(contract.canProveRole(ROLE_OP3)).toBe(true); + it('should prove all roles with same key', async () => { + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(true); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); + expect(await contract.canProveRole(ROLE_OP2)).toBe(true); + expect(await contract.canProveRole(ROLE_OP3)).toBe(true); }); - it('revoking one role should not affect others', () => { - contract._revokeRole(ROLE_OP2, ADMIN_ACCOUNT_ID); + it('revoking one role should not affect others', async () => { + await contract._revokeRole(ROLE_OP2, ADMIN_ACCOUNT_ID); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(true); - expect(contract.canProveRole(ROLE_OP1)).toBe(true); - expect(contract.canProveRole(ROLE_OP2)).toBe(false); - expect(contract.canProveRole(ROLE_OP3)).toBe(true); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(true); + expect(await contract.canProveRole(ROLE_OP1)).toBe(true); + expect(await contract.canProveRole(ROLE_OP2)).toBe(false); + expect(await contract.canProveRole(ROLE_OP3)).toBe(true); }); }); describe('cross-contract isolation', () => { - it('should not validate a role granted on a different contract instance', () => { - contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); - expect(contract.canProveRole(ROLE_ADMIN)).toBe(true); + it('should not validate a role granted on a different contract instance', async () => { + await contract._grantRole(ROLE_ADMIN, ADMIN_ACCOUNT_ID); + expect(await contract.canProveRole(ROLE_ADMIN)).toBe(true); // Deploy a different contract with a different salt const differentSalt = new Uint8Array(32).fill(99); - const contractB = new ShieldedAccessControlSimulator( + const contractB = await ShieldedAccessControlSimulator.create( differentSalt, true, { @@ -1198,12 +1208,12 @@ describe('ShieldedAccessControl', () => { // Same key on contract B produces a different accountId (different salt) // so canProveRole should return false — role was never granted on B - expect(contractB.canProveRole(ROLE_ADMIN)).toBe(false); + expect(await contractB.canProveRole(ROLE_ADMIN)).toBe(false); }); - it('should produce different commitments for same role and key across instances', () => { + it('should produce different commitments for same role and key across instances', async () => { const differentSalt = new Uint8Array(32).fill(99); - const contractB = new ShieldedAccessControlSimulator( + const contractB = await ShieldedAccessControlSimulator.create( differentSalt, true, { @@ -1212,15 +1222,15 @@ describe('ShieldedAccessControl', () => { }, ); - const commitmentA = contract.computeRoleCommitment( + const commitmentA = await contract.computeRoleCommitment( ROLE_ADMIN, ADMIN_ACCOUNT_ID, ); - const accountIdOnB = contractB.computeAccountId( + const accountIdOnB = await contractB.computeAccountId( ADMIN_SK, differentSalt, ); - const commitmentB = contractB.computeRoleCommitment( + const commitmentB = await contractB.computeRoleCommitment( ROLE_ADMIN, accountIdOnB, ); @@ -1231,46 +1241,54 @@ describe('ShieldedAccessControl', () => { }); describe('privateState helpers', () => { - beforeEach(() => { - contract = new ShieldedAccessControlSimulator(INSTANCE_SALT, true, { - privateState: ShieldedAccessControlPrivateState.withSecretKey(ADMIN_SK), - }); + beforeEach(async () => { + contract = await ShieldedAccessControlSimulator.create( + INSTANCE_SALT, + true, + { + privateState: + ShieldedAccessControlPrivateState.withSecretKey(ADMIN_SK), + }, + ); }); describe('getCurrentSecretKey', () => { - it('should return the secret key from private state', () => { - expect(contract.privateState.getCurrentSecretKey()).toEqual(ADMIN_SK); + it('should return the secret key from private state', async () => { + expect(await contract.privateState.getCurrentSecretKey()).toEqual( + ADMIN_SK, + ); }); - it('should throw when the secret key is undefined', () => { - contract.privateState.injectSecretKey(undefined as never); + it('should throw when the secret key is undefined', async () => { + await contract.privateState.injectSecretKey(undefined as never); - expect(() => contract.privateState.getCurrentSecretKey()).toThrow( - 'Missing secret key', - ); + await expect( + contract.privateState.getCurrentSecretKey(), + ).rejects.toThrow('Missing secret key'); }); }); describe('getCommitmentPathWithFindForLeaf', () => { - it('should return undefined when the commitment is not in the tree', () => { + it('should return undefined when the commitment is not in the tree', async () => { const absentCommitment = buildRoleCommitmentHash( ROLE_NONEXISTENT, BAD_ACCOUNT_ID, ); expect( - contract.privateState.getCommitmentPathWithFindForLeaf( + await contract.privateState.getCommitmentPathWithFindForLeaf( absentCommitment, ), ).toBeUndefined(); }); - it('should return a path when the commitment is in the tree', () => { - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + it('should return a path when the commitment is in the tree', async () => { + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); - const path = contract.privateState.getCommitmentPathWithFindForLeaf( - OP1_ROLE_COMMITMENT, - ); + const path = + await contract.privateState.getCommitmentPathWithFindForLeaf( + OP1_ROLE_COMMITMENT, + ); expect(path).toBeDefined(); expect(path?.leaf).toEqual(OP1_ROLE_COMMITMENT); @@ -1278,30 +1296,31 @@ describe('ShieldedAccessControl', () => { }); describe('getCommitmentPathWithWitnessImpl', () => { - it('should return a default path when the commitment is not in the tree', () => { + it('should return a default path when the commitment is not in the tree', async () => { const absentCommitment = buildRoleCommitmentHash( ROLE_NONEXISTENT, BAD_ACCOUNT_ID, ); const path = - contract.privateState.getCommitmentPathWithWitnessImpl( + await contract.privateState.getCommitmentPathWithWitnessImpl( absentCommitment, ); expect(path.leaf).toEqual(new Uint8Array(32)); }); - it('should return a path matching findPathForLeaf when the commitment is in the tree', () => { - contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); + it('should return a path matching findPathForLeaf when the commitment is in the tree', async () => { + await contract._grantRole(ROLE_OP1, OP1_ACCOUNT_ID); const witnessPath = - contract.privateState.getCommitmentPathWithWitnessImpl( + await contract.privateState.getCommitmentPathWithWitnessImpl( + OP1_ROLE_COMMITMENT, + ); + const findPath = + await contract.privateState.getCommitmentPathWithFindForLeaf( OP1_ROLE_COMMITMENT, ); - const findPath = contract.privateState.getCommitmentPathWithFindForLeaf( - OP1_ROLE_COMMITMENT, - ); expect(witnessPath.leaf).toEqual(findPath?.leaf); }); diff --git a/contracts/src/access/test/ZOwnablePK.test.ts b/contracts/src/access/test/ZOwnablePK.test.ts index d0fc41b6..6c6f971f 100644 --- a/contracts/src/access/test/ZOwnablePK.test.ts +++ b/contracts/src/access/test/ZOwnablePK.test.ts @@ -7,13 +7,12 @@ import { import { beforeEach, describe, expect, it } from 'vitest'; import * as utils from '#test-utils/address.js'; import type { ZswapCoinPublicKey } from '../../../artifacts/MockOwnable/contract/index.js'; -import { ZOwnablePKPrivateState } from './witnesses/ZOwnablePKWitnesses.js'; import { ZOwnablePKSimulator } from './simulators/ZOwnablePKSimulator.js'; +import { ZOwnablePKPrivateState } from './witnesses/ZOwnablePKWitnesses.js'; // PKs -const [OWNER, Z_OWNER] = utils.generatePubKeyPair('OWNER'); -const [NEW_OWNER, Z_NEW_OWNER] = utils.generatePubKeyPair('NEW_OWNER'); -const [UNAUTHORIZED, _] = utils.generatePubKeyPair('UNAUTHORIZED'); +const [, Z_OWNER] = utils.generatePubKeyPair('OWNER'); +const [, Z_NEW_OWNER] = utils.generatePubKeyPair('NEW_OWNER'); const INSTANCE_SALT = new Uint8Array(32).fill(8675309); const BAD_NONCE = Buffer.from(Buffer.alloc(32, 'BAD_NONCE')); @@ -77,33 +76,37 @@ const buildCommitment = ( describe('ZOwnablePK', () => { describe('before initialize', () => { - it('should fail when setting owner commitment as 0', () => { - expect(() => { - const badId = new Uint8Array(32).fill(0); - new ZOwnablePKSimulator(badId, INSTANCE_SALT, isInit); - }).toThrow('ZOwnablePK: invalid id'); + it('should fail when setting owner commitment as 0', async () => { + const badId = new Uint8Array(32).fill(0); + await expect( + ZOwnablePKSimulator.create(badId, INSTANCE_SALT, isInit), + ).rejects.toThrow('ZOwnablePK: invalid id'); }); - it('should initialize with non-zero commitment', () => { + it('should initialize with non-zero commitment', async () => { const notZeroPK = utils.encodeToPK('NOT_ZERO'); const notZeroNonce = new Uint8Array(32).fill(1); const nonZeroId = createIdHash(notZeroPK, notZeroNonce); - ownable = new ZOwnablePKSimulator(nonZeroId, INSTANCE_SALT, isInit); + ownable = await ZOwnablePKSimulator.create( + nonZeroId, + INSTANCE_SALT, + isInit, + ); const nonZeroCommitment = buildCommitmentFromId( nonZeroId, INSTANCE_SALT, INIT_COUNTER, ); - expect(ownable.owner()).toEqual(nonZeroCommitment); + expect(await ownable.owner()).toEqual(nonZeroCommitment); }); }); describe('when not initialized correctly', () => { const isNotInit = false; - beforeEach(() => { - ownable = new ZOwnablePKSimulator( + beforeEach(async () => { + ownable = await ZOwnablePKSimulator.create( randomByteArray, INSTANCE_SALT, isNotInit, @@ -121,23 +124,25 @@ describe('ZOwnablePK', () => { ['_computeOwnerCommitment', [randomByteArray, randomCounter]], ['_transferOwnership', [randomByteArray]], ]; - it.each(circuitsToFail)('%s should fail', (circuitName, args) => { - expect(() => { - (ownable[circuitName] as (...args: unknown[]) => unknown)(...args); - }).toThrow('ZOwnablePK: contract not initialized'); + it.each(circuitsToFail)('%s should fail', async (circuitName, args) => { + await expect( + (ownable[circuitName] as (...args: unknown[]) => Promise)( + ...args, + ), + ).rejects.toThrow('ZOwnablePK: contract not initialized'); }); - it('should allow pure computeOwnerId', () => { + it('should allow pure computeOwnerId', async () => { const eitherOwner = utils.createEitherTestUser('OWNER'); - expect(() => { - ownable._computeOwnerId(eitherOwner, randomByteArray); - }).not.toThrow(); + await expect( + ownable._computeOwnerId(eitherOwner, randomByteArray), + ).resolves.not.toThrow(); }); }); describe('after initialization', () => { - beforeEach(() => { + beforeEach(async () => { // Create private state object and generate nonce const PS = ZOwnablePKPrivateState.generate(); // Bind nonce for convenience @@ -145,13 +150,18 @@ describe('ZOwnablePK', () => { // Prepare owner ID with gen nonce const ownerId = createIdHash(Z_OWNER, secretNonce); // Deploy contract with derived owner commitment and PS - ownable = new ZOwnablePKSimulator(ownerId, INSTANCE_SALT, isInit, { - privateState: PS, - }); + ownable = await ZOwnablePKSimulator.create( + ownerId, + INSTANCE_SALT, + isInit, + { + privateState: PS, + }, + ); }); describe('owner', () => { - it('should return the correct owner commitment', () => { + it('should return the correct owner commitment', async () => { const expCommitment = buildCommitment( Z_OWNER, secretNonce, @@ -159,7 +169,7 @@ describe('ZOwnablePK', () => { INIT_COUNTER, DOMAIN, ); - expect(ownable.owner()).toEqual(expCommitment); + expect(await ownable.owner()).toEqual(expCommitment); }); }); @@ -183,58 +193,62 @@ describe('ZOwnablePK', () => { ); }); - it('should transfer ownership', () => { - ownable.as(OWNER).transferOwnership(newIdHash); - expect(ownable.owner()).toEqual(newOwnerCommitment); + it('should transfer ownership', async () => { + await ownable.as('OWNER').transferOwnership(newIdHash); + expect(await ownable.owner()).toEqual(newOwnerCommitment); // Old owner - expect(() => { - ownable.as(OWNER).assertOnlyOwner(); - }).toThrow('ZOwnablePK: caller is not the owner'); + await expect(ownable.as('OWNER').assertOnlyOwner()).rejects.toThrow( + 'ZOwnablePK: caller is not the owner', + ); // Unauthorized - expect(() => { - ownable.as(UNAUTHORIZED).assertOnlyOwner(); - }).toThrow('ZOwnablePK: caller is not the owner'); + await expect( + ownable.as('UNAUTHORIZED').assertOnlyOwner(), + ).rejects.toThrow('ZOwnablePK: caller is not the owner'); // New owner - ownable.privateState.injectSecretNonce(Buffer.from(newOwnerNonce)); - expect(() => { - ownable.as(NEW_OWNER).assertOnlyOwner(); - }).not.toThrow(); + await ownable.privateState.injectSecretNonce( + Buffer.from(newOwnerNonce), + ); + await expect( + ownable.as('NEW_OWNER').assertOnlyOwner(), + ).resolves.not.toThrow(); }); - it('should fail when transferring to id zero', () => { + it('should fail when transferring to id zero', async () => { const badId = new Uint8Array(32).fill(0); - expect(() => { - ownable.as(OWNER).transferOwnership(badId); - }).toThrow('ZOwnablePK: invalid id'); + await expect( + ownable.as('OWNER').transferOwnership(badId), + ).rejects.toThrow('ZOwnablePK: invalid id'); }); - it('should fail when unauthorized transfers ownership', () => { - expect(() => { - ownable.as(UNAUTHORIZED).transferOwnership(newOwnerCommitment); - }).toThrow('ZOwnablePK: caller is not the owner'); + it('should fail when unauthorized transfers ownership', async () => { + await expect( + ownable.as('UNAUTHORIZED').transferOwnership(newOwnerCommitment), + ).rejects.toThrow('ZOwnablePK: caller is not the owner'); }); /** * @description More thoroughly tested in `_transferOwnership` * */ - it('should bump instance after transfer', () => { - const beforeInstance = ownable.getPublicState().ZOwnablePK__counter; + it('should bump instance after transfer', async () => { + const beforeInstance = (await ownable.getPublicState()) + .ZOwnablePK__counter; // Transfer - ownable.as(OWNER).transferOwnership(newOwnerCommitment); + await ownable.as('OWNER').transferOwnership(newOwnerCommitment); // Check counter - const afterInstance = ownable.getPublicState().ZOwnablePK__counter; + const afterInstance = (await ownable.getPublicState()) + .ZOwnablePK__counter; expect(afterInstance).toEqual(beforeInstance + 1n); }); - it('should change commitment when transferring ownership to self with same pk + nonce)', () => { + it('should change commitment when transferring ownership to self with same pk + nonce)', async () => { // Confirm current commitment const repeatedId = createIdHash(Z_OWNER, secretNonce); - const initCommitment = ownable.owner(); + const initCommitment = await ownable.owner(); const expInitCommitment = buildCommitmentFromId( repeatedId, INSTANCE_SALT, @@ -243,10 +257,10 @@ describe('ZOwnablePK', () => { expect(initCommitment).toEqual(expInitCommitment); // Transfer ownership to self with the same id -> `H(pk, nonce)` - ownable.as(OWNER).transferOwnership(repeatedId); + await ownable.as('OWNER').transferOwnership(repeatedId); // Check commitments don't match - const newCommitment = ownable.owner(); + const newCommitment = await ownable.owner(); expect(initCommitment).not.toEqual(newCommitment); // Build commitment locally and validate new commitment == expected @@ -259,97 +273,97 @@ describe('ZOwnablePK', () => { expect(newCommitment).toEqual(expNewCommitment); // Check same owner maintains permissions after transfer - expect(() => { - ownable.as(OWNER).assertOnlyOwner(); - }).not.toThrow(); + await expect( + ownable.as('OWNER').assertOnlyOwner(), + ).resolves.not.toThrow(); }); }); describe('renounceOwnership', () => { - it('should renounce ownership', () => { - ownable.as(OWNER).renounceOwnership(); + it('should renounce ownership', async () => { + await ownable.as('OWNER').renounceOwnership(); // Check owner is reset - expect(ownable.owner()).toEqual(new Uint8Array(32).fill(0)); + expect(await ownable.owner()).toEqual(new Uint8Array(32).fill(0)); // Check revoked permissions - expect(() => { - ownable.as(OWNER).assertOnlyOwner(); - }).toThrow('ZOwnablePK: caller is not the owner'); + await expect(ownable.as('OWNER').assertOnlyOwner()).rejects.toThrow( + 'ZOwnablePK: caller is not the owner', + ); }); - it('should fail when renouncing from unauthorized', () => { - expect(() => { - ownable.as(UNAUTHORIZED).renounceOwnership(); - }).toThrow('ZOwnablePK: caller is not the owner'); + it('should fail when renouncing from unauthorized', async () => { + await expect( + ownable.as('UNAUTHORIZED').renounceOwnership(), + ).rejects.toThrow('ZOwnablePK: caller is not the owner'); }); - it('should fail when renouncing from authorized with bad nonce', () => { - ownable.privateState.injectSecretNonce(BAD_NONCE); - expect(() => { - ownable.as(OWNER).renounceOwnership(); - }).toThrow('ZOwnablePK: caller is not the owner'); + it('should fail when renouncing from authorized with bad nonce', async () => { + await ownable.privateState.injectSecretNonce(BAD_NONCE); + await expect(ownable.as('OWNER').renounceOwnership()).rejects.toThrow( + 'ZOwnablePK: caller is not the owner', + ); }); - it('should fail when renouncing from unauthorized with bad nonce', () => { - ownable.privateState.injectSecretNonce(BAD_NONCE); - expect(() => { - ownable.as(UNAUTHORIZED).renounceOwnership(); - }).toThrow('ZOwnablePK: caller is not the owner'); + it('should fail when renouncing from unauthorized with bad nonce', async () => { + await ownable.privateState.injectSecretNonce(BAD_NONCE); + await expect( + ownable.as('UNAUTHORIZED').renounceOwnership(), + ).rejects.toThrow('ZOwnablePK: caller is not the owner'); }); }); describe('assertOnlyOwner', () => { - it('should allow authorized caller with correct nonce to call', () => { + it('should allow authorized caller with correct nonce to call', async () => { // Check nonce is correct - expect(ownable.privateState.getCurrentSecretNonce()).toEqual( + expect(await ownable.privateState.getCurrentSecretNonce()).toEqual( secretNonce, ); - expect(() => { - ownable.as(OWNER).assertOnlyOwner(); - }).not.toThrow(); + await expect( + ownable.as('OWNER').assertOnlyOwner(), + ).resolves.not.toThrow(); }); - it('should fail when the authorized caller has the wrong nonce', () => { + it('should fail when the authorized caller has the wrong nonce', async () => { // Inject bad nonce - ownable.privateState.injectSecretNonce(BAD_NONCE); + await ownable.privateState.injectSecretNonce(BAD_NONCE); // Check nonce does not match - expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( + expect(await ownable.privateState.getCurrentSecretNonce()).not.toEqual( secretNonce, ); // Set caller and call circuit - expect(() => { - ownable.as(OWNER).assertOnlyOwner(); - }).toThrow('ZOwnablePK: caller is not the owner'); + await expect(ownable.as('OWNER').assertOnlyOwner()).rejects.toThrow( + 'ZOwnablePK: caller is not the owner', + ); }); - it('should fail when unauthorized caller has the correct nonce', () => { + it('should fail when unauthorized caller has the correct nonce', async () => { // Check nonce is correct - expect(ownable.privateState.getCurrentSecretNonce()).toEqual( + expect(await ownable.privateState.getCurrentSecretNonce()).toEqual( secretNonce, ); - expect(() => { - ownable.as(UNAUTHORIZED).assertOnlyOwner(); - }).toThrow('ZOwnablePK: caller is not the owner'); + await expect( + ownable.as('UNAUTHORIZED').assertOnlyOwner(), + ).rejects.toThrow('ZOwnablePK: caller is not the owner'); }); - it('should fail when unauthorized caller has the wrong nonce', () => { + it('should fail when unauthorized caller has the wrong nonce', async () => { // Inject bad nonce - ownable.privateState.injectSecretNonce(BAD_NONCE); + await ownable.privateState.injectSecretNonce(BAD_NONCE); // Check nonce does not match - expect(ownable.privateState.getCurrentSecretNonce()).not.toEqual( + expect(await ownable.privateState.getCurrentSecretNonce()).not.toEqual( secretNonce, ); // Set unauthorized caller and call circuit - expect(() => { - ownable.as(UNAUTHORIZED).assertOnlyOwner(); - }).toThrow('ZOwnablePK: caller is not the owner'); + await expect( + ownable.as('UNAUTHORIZED').assertOnlyOwner(), + ).rejects.toThrow('ZOwnablePK: caller is not the owner'); }); }); @@ -374,14 +388,17 @@ describe('ZOwnablePK', () => { ]; it.each( testCases, - )('should match commitment for $label with counter $counter', ({ + )('should match commitment for $label with counter $counter', async ({ ownerPK, counter, }) => { const id = createIdHash(ownerPK, secretNonce); // Check buildCommitmentFromId - const hashFromContract = ownable._computeOwnerCommitment(id, counter); + const hashFromContract = await ownable._computeOwnerCommitment( + id, + counter, + ); const hashFromHelper1 = buildCommitmentFromId( id, INSTANCE_SALT, @@ -422,28 +439,30 @@ describe('ZOwnablePK', () => { it.each( testCases, - )('should match local and contract owner id for $label', ({ + )('should match local and contract owner id for $label', async ({ eitherOwner, nonce, }) => { - const ownerId = ownable._computeOwnerId(eitherOwner, nonce); + const ownerId = await ownable._computeOwnerId(eitherOwner, nonce); const expId = createIdHash(eitherOwner.left, nonce); expect(ownerId).toEqual(expId); }); - it('should fail to compute ContractAddress id', () => { + it('should fail to compute ContractAddress id', async () => { const eitherContract = utils.createEitherTestContractAddress('CONTRACT'); - expect(() => { - ownable._computeOwnerId(eitherContract, secretNonce); - }).toThrow('ZOwnablePK: contract address owners are not yet supported'); + await expect( + ownable._computeOwnerId(eitherContract, secretNonce), + ).rejects.toThrow( + 'ZOwnablePK: contract address owners are not yet supported', + ); }); }); describe('_transferOwnership', () => { - it('should transfer ownership', () => { + it('should transfer ownership', async () => { const id = createIdHash(Z_OWNER, secretNonce); - ownable._transferOwnership(id); + await ownable._transferOwnership(id); const nextCounter = INIT_COUNTER + 1n; const expCommitment = buildCommitmentFromId( @@ -451,27 +470,27 @@ describe('ZOwnablePK', () => { INSTANCE_SALT, nextCounter, ); - expect(ownable.owner()).toEqual(expCommitment); + expect(await ownable.owner()).toEqual(expCommitment); }); - it('should bump the counter with each transfer', () => { + it('should bump the counter with each transfer', async () => { const nTransfers = 10; const counterStart = 2; // count starts at 2 bc the constructor bumps the count to 1 for (let i = counterStart; i <= nTransfers; i++) { const pk = utils.encodeToPK(`Id${i}`); const nonce = new Uint8Array(32).fill(i); const id = createIdHash(pk, nonce); - ownable._transferOwnership(id); + await ownable._transferOwnership(id); - expect(ownable.getPublicState().ZOwnablePK__counter).toEqual( + expect((await ownable.getPublicState()).ZOwnablePK__counter).toEqual( BigInt(i), ); } }); - it('should allow transfer to all zeroes id', () => { + it('should allow transfer to all zeroes id', async () => { const zeroId = utils.zeroUint8Array(); - ownable._transferOwnership(zeroId); + await ownable._transferOwnership(zeroId); const nextCounter = INIT_COUNTER + 1n; const expCommitment = buildCommitmentFromId( @@ -479,18 +498,18 @@ describe('ZOwnablePK', () => { INSTANCE_SALT, nextCounter, ); - expect(ownable.owner()).toEqual(expCommitment); + expect(await ownable.owner()).toEqual(expCommitment); }); - it('should allow anyone to transfer', () => { + it('should allow anyone to transfer', async () => { const id = createIdHash(Z_OWNER, secretNonce); - expect(() => { - ownable.as(OWNER)._transferOwnership(id); - }).not.toThrow(); + await expect( + ownable.as('OWNER')._transferOwnership(id), + ).resolves.not.toThrow(); - expect(() => { - ownable.as(UNAUTHORIZED)._transferOwnership(id); - }).not.toThrow(); + await expect( + ownable.as('UNAUTHORIZED')._transferOwnership(id), + ).resolves.not.toThrow(); }); }); }); diff --git a/contracts/src/access/test/simulators/AccessControlSimulator.ts b/contracts/src/access/test/simulators/AccessControlSimulator.ts index 3314b2f5..e45ba80a 100644 --- a/contracts/src/access/test/simulators/AccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/AccessControlSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -31,26 +31,28 @@ const AccessControlSimulatorBase = createSimulator< contractArgs: () => [], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => AccessControlWitnesses(), + artifactName: 'MockAccessControl', }); /** * AccessControl Simulator */ export class AccessControlSimulator extends AccessControlSimulatorBase { - constructor( - options: BaseSimulatorOptions< + static async create( + options: SimulatorOptions< AccessControlPrivateState, ReturnType > = {}, - ) { - super([], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create([], options) as Promise; } /** * @description Returns the default admin role identifier. * @returns The default admin role identifier (zero bytes). */ - public DEFAULT_ADMIN_ROLE(): Uint8Array { + public DEFAULT_ADMIN_ROLE(): Promise { return this.circuits.pure.DEFAULT_ADMIN_ROLE(); } @@ -63,7 +65,7 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { public hasRole( roleId: Uint8Array, account: Either, - ): boolean { + ): Promise { return this.circuits.impure.hasRole(roleId, account); } @@ -71,8 +73,8 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { * @description Retrieves an account's permission for `roleId`. * @param roleId - The role identifier. */ - public assertOnlyRole(roleId: Uint8Array) { - this.circuits.impure.assertOnlyRole(roleId); + public assertOnlyRole(roleId: Uint8Array): Promise<[]> { + return this.circuits.impure.assertOnlyRole(roleId); } /** @@ -83,8 +85,8 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { public _checkRole( roleId: Uint8Array, account: Either, - ) { - this.circuits.impure._checkRole(roleId, account); + ): Promise<[]> { + return this.circuits.impure._checkRole(roleId, account); } /** @@ -92,7 +94,7 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { * @param roleId - The role identifier. * @returns The admin identifier for `roleId`. */ - public getRoleAdmin(roleId: Uint8Array): Uint8Array { + public getRoleAdmin(roleId: Uint8Array): Promise { return this.circuits.impure.getRoleAdmin(roleId); } @@ -104,8 +106,8 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { public grantRole( roleId: Uint8Array, account: Either, - ) { - this.circuits.impure.grantRole(roleId, account); + ): Promise<[]> { + return this.circuits.impure.grantRole(roleId, account); } /** @@ -116,8 +118,8 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { public revokeRole( roleId: Uint8Array, account: Either, - ) { - this.circuits.impure.revokeRole(roleId, account); + ): Promise<[]> { + return this.circuits.impure.revokeRole(roleId, account); } /** @@ -128,8 +130,8 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { public renounceRole( roleId: Uint8Array, account: Either, - ) { - this.circuits.impure.renounceRole(roleId, account); + ): Promise<[]> { + return this.circuits.impure.renounceRole(roleId, account); } /** @@ -137,8 +139,8 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { * @param roleId - The role identifier. * @param adminId - The admin role identifier. */ - public _setRoleAdmin(roleId: Uint8Array, adminId: Uint8Array) { - this.circuits.impure._setRoleAdmin(roleId, adminId); + public _setRoleAdmin(roleId: Uint8Array, adminId: Uint8Array): Promise<[]> { + return this.circuits.impure._setRoleAdmin(roleId, adminId); } /** @@ -149,7 +151,7 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { public _grantRole( roleId: Uint8Array, account: Either, - ): boolean { + ): Promise { return this.circuits.impure._grantRole(roleId, account); } @@ -162,7 +164,7 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { public _unsafeGrantRole( roleId: Uint8Array, account: Either, - ): boolean { + ): Promise { return this.circuits.impure._unsafeGrantRole(roleId, account); } @@ -174,7 +176,7 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { public _revokeRole( roleId: Uint8Array, account: Either, - ): boolean { + ): Promise { return this.circuits.impure._revokeRole(roleId, account); } @@ -186,14 +188,16 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { * @param newSK - The new secret key to set. * @returns The updated private state. */ - injectSecretKey: (newSK: Uint8Array): AccessControlPrivateState => { - const currentState = this.getPrivateState(); - const updatedState = { - ...currentState, + injectSecretKey: async ( + newSK: Uint8Array, + ): Promise => { + const cur = await this.getPrivateState(); + const updated = { + ...cur, ...AccessControlPrivateState.withSecretKey(newSK), }; - this.circuitContextManager.updatePrivateState(updatedState); - return updatedState; + this.setPrivateState(updated); + return updated; }, /** @@ -201,8 +205,8 @@ export class AccessControlSimulator extends AccessControlSimulatorBase { * @returns The secret key. * @throws If the secret key is undefined. */ - getCurrentSecretKey: (): Uint8Array => { - const sk = this.getPrivateState().secretKey; + getCurrentSecretKey: async (): Promise => { + const sk = (await this.getPrivateState()).secretKey; if (typeof sk === 'undefined') { throw new Error('Missing secret key'); } diff --git a/contracts/src/access/test/simulators/OwnableSimulator.ts b/contracts/src/access/test/simulators/OwnableSimulator.ts index 2e14f4e1..98a79f42 100644 --- a/contracts/src/access/test/simulators/OwnableSimulator.ts +++ b/contracts/src/access/test/simulators/OwnableSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -34,27 +34,32 @@ const OwnableSimulatorBase = createSimulator< contractArgs: (initialOwner, isInit) => [initialOwner, isInit], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => OwnableWitnesses(), + artifactName: 'MockOwnable', }); /** * Ownable Simulator */ export class OwnableSimulator extends OwnableSimulatorBase { - constructor( + static async create( initialOwner: Either, isInit: boolean, - options: BaseSimulatorOptions< + options: SimulatorOptions< OwnablePrivateState, ReturnType > = {}, - ) { - super([initialOwner, isInit], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [initialOwner, isInit], + options, + ) as Promise; } /** * @description Returns the current contract owner. * @returns The contract owner. */ - public owner(): Either { + public owner(): Promise> { return this.circuits.impure.owner(); } @@ -62,8 +67,10 @@ export class OwnableSimulator extends OwnableSimulatorBase { * @description Transfers ownership of the contract to `newOwner`. * @param newOwner - The new owner. */ - public transferOwnership(newOwner: Either) { - this.circuits.impure.transferOwnership(newOwner); + public transferOwnership( + newOwner: Either, + ): Promise<[]> { + return this.circuits.impure.transferOwnership(newOwner); } /** @@ -72,8 +79,8 @@ export class OwnableSimulator extends OwnableSimulatorBase { */ public _unsafeTransferOwnership( newOwner: Either, - ) { - this.circuits.impure._unsafeTransferOwnership(newOwner); + ): Promise<[]> { + return this.circuits.impure._unsafeTransferOwnership(newOwner); } /** @@ -81,16 +88,16 @@ export class OwnableSimulator extends OwnableSimulatorBase { * It will not be possible to call `assertOnlyOnwer` circuits anymore. * Can only be called by the current owner. */ - public renounceOwnership() { - this.circuits.impure.renounceOwnership(); + public renounceOwnership(): Promise<[]> { + return this.circuits.impure.renounceOwnership(); } /** * @description Throws if called by any account other than the owner. * Use this to restrict access of specific circuits to the owner. */ - public assertOnlyOwner() { - this.circuits.impure.assertOnlyOwner(); + public assertOnlyOwner(): Promise<[]> { + return this.circuits.impure.assertOnlyOwner(); } /** @@ -98,8 +105,10 @@ export class OwnableSimulator extends OwnableSimulatorBase { * enforcing permission checks on the caller. * @param newOwner - The new owner. */ - public _transferOwnership(newOwner: Either) { - this.circuits.impure._transferOwnership(newOwner); + public _transferOwnership( + newOwner: Either, + ): Promise<[]> { + return this.circuits.impure._transferOwnership(newOwner); } /** @@ -108,8 +117,8 @@ export class OwnableSimulator extends OwnableSimulatorBase { */ public _unsafeUncheckedTransferOwnership( newOwner: Either, - ) { - this.circuits.impure._unsafeUncheckedTransferOwnership(newOwner); + ): Promise<[]> { + return this.circuits.impure._unsafeUncheckedTransferOwnership(newOwner); } public readonly privateState = { @@ -120,10 +129,12 @@ export class OwnableSimulator extends OwnableSimulatorBase { * @param newSK - The new secret key to set. * @returns The updated private state. */ - injectSecretKey: (newSK: Uint8Array): OwnablePrivateState => { - const updatedState = OwnablePrivateState.withSecretKey(newSK); - this.circuitContextManager.updatePrivateState(updatedState); - return updatedState; + injectSecretKey: async ( + newSK: Uint8Array, + ): Promise => { + const updated = OwnablePrivateState.withSecretKey(newSK); + this.setPrivateState(updated); + return updated; }, /** @@ -131,8 +142,8 @@ export class OwnableSimulator extends OwnableSimulatorBase { * @returns The secret key. * @throws If the secret key is undefined. */ - getCurrentSecretKey: (): Uint8Array => { - const sk = this.getPrivateState().secretKey; + getCurrentSecretKey: async (): Promise => { + const sk = (await this.getPrivateState()).secretKey; if (typeof sk === 'undefined') { throw new Error('Missing secret key'); } diff --git a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts index 05c3858a..0c84864d 100644 --- a/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts +++ b/contracts/src/access/test/simulators/ShieldedAccessControlSimulator.ts @@ -1,7 +1,10 @@ -import type { MerkleTreePath } from '@midnight-ntwrk/compact-runtime'; +import type { + MerkleTreePath, + WitnessContext, +} from '@midnight-ntwrk/compact-runtime'; import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { ledger, @@ -38,78 +41,86 @@ const ShieldedAccessControlSimulatorBase = createSimulator< ledgerExtractor: (state) => ledger(state), witnessesFactory: () => ShieldedAccessControlWitnesses(), + artifactName: 'MockShieldedAccessControl', }); /** * ShieldedAccessControlSimulator */ export class ShieldedAccessControlSimulator extends ShieldedAccessControlSimulatorBase { - constructor( + static async create( instanceSalt: Uint8Array, isInit: boolean, - options: BaseSimulatorOptions< + options: SimulatorOptions< ShieldedAccessControlPrivateState, ReturnType > = {}, - ) { - super([instanceSalt, isInit], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [instanceSalt, isInit], + options, + ) as Promise; } - public DEFAULT_ADMIN_ROLE(): Uint8Array { + public DEFAULT_ADMIN_ROLE(): Promise { return this.circuits.pure.DEFAULT_ADMIN_ROLE(); } - public assertOnlyRole(role: Uint8Array) { - this.circuits.impure.assertOnlyRole(role); + public assertOnlyRole(role: Uint8Array): Promise<[]> { + return this.circuits.impure.assertOnlyRole(role); } - public canProveRole(role: Uint8Array): boolean { + public canProveRole(role: Uint8Array): Promise { return this.circuits.impure.canProveRole(role); } - public grantRole(role: Uint8Array, accountId: Uint8Array) { - this.circuits.impure.grantRole(role, accountId); + public grantRole(role: Uint8Array, accountId: Uint8Array): Promise<[]> { + return this.circuits.impure.grantRole(role, accountId); } - public _grantRole(role: Uint8Array, accountId: Uint8Array) { - this.circuits.impure._grantRole(role, accountId); + public _grantRole(role: Uint8Array, accountId: Uint8Array): Promise<[]> { + return this.circuits.impure._grantRole(role, accountId); } - public renounceRole(role: Uint8Array, callerConfirmation: Uint8Array) { - this.circuits.impure.renounceRole(role, callerConfirmation); + public renounceRole( + role: Uint8Array, + callerConfirmation: Uint8Array, + ): Promise<[]> { + return this.circuits.impure.renounceRole(role, callerConfirmation); } - public revokeRole(role: Uint8Array, accountId: Uint8Array) { - this.circuits.impure.revokeRole(role, accountId); + public revokeRole(role: Uint8Array, accountId: Uint8Array): Promise<[]> { + return this.circuits.impure.revokeRole(role, accountId); } - public _revokeRole(role: Uint8Array, accountId: Uint8Array) { - this.circuits.impure._revokeRole(role, accountId); + public _revokeRole(role: Uint8Array, accountId: Uint8Array): Promise<[]> { + return this.circuits.impure._revokeRole(role, accountId); } - public getRoleAdmin(role: Uint8Array): Uint8Array { + public getRoleAdmin(role: Uint8Array): Promise { return this.circuits.impure.getRoleAdmin(role); } - public _setRoleAdmin(role: Uint8Array, adminRole: Uint8Array) { - this.circuits.impure._setRoleAdmin(role, adminRole); + public _setRoleAdmin(role: Uint8Array, adminRole: Uint8Array): Promise<[]> { + return this.circuits.impure._setRoleAdmin(role, adminRole); } public computeRoleCommitment( role: Uint8Array, accountId: Uint8Array, - ): Uint8Array { + ): Promise { return this.circuits.impure.computeRoleCommitment(role, accountId); } - public computeNullifier(roleCommitment: Uint8Array): Uint8Array { + public computeNullifier(roleCommitment: Uint8Array): Promise { return this.circuits.pure.computeNullifier(roleCommitment); } public computeAccountId( secretKey: Uint8Array, instanceSalt: Uint8Array, - ): Uint8Array { + ): Promise { return this.circuits.pure.computeAccountId(secretKey, instanceSalt); } @@ -121,12 +132,12 @@ export class ShieldedAccessControlSimulator extends ShieldedAccessControlSimulat * @param newSK - The new secret key to set. * @returns The updated private state. */ - injectSecretKey: ( + injectSecretKey: async ( newSK: Buffer, - ): ShieldedAccessControlPrivateState => { - const updatedState = { secretKey: newSK }; - this.circuitContextManager.updatePrivateState(updatedState); - return updatedState; + ): Promise => { + const updated = { secretKey: newSK }; + this.setPrivateState(updated); + return updated; }, /** @@ -134,8 +145,8 @@ export class ShieldedAccessControlSimulator extends ShieldedAccessControlSimulat * @returns The secret key. * @throws If the secret key is undefined. */ - getCurrentSecretKey: (): Uint8Array => { - const sk = this.getPrivateState().secretKey; + getCurrentSecretKey: async (): Promise => { + const sk = (await this.getPrivateState()).secretKey; if (typeof sk === 'undefined') { throw new Error('Missing secret key'); } @@ -149,12 +160,12 @@ export class ShieldedAccessControlSimulator extends ShieldedAccessControlSimulat * @param roleCommitment - The role commitment to search for. * @returns The Merkle tree path if the commitment exists, undefined otherwise. */ - getCommitmentPathWithFindForLeaf: ( + getCommitmentPathWithFindForLeaf: async ( roleCommitment: Uint8Array, - ): MerkleTreePath | undefined => { - return this.getPublicState().ShieldedAccessControl__operatorRoles.findPathForLeaf( - roleCommitment, - ); + ): Promise | undefined> => { + return ( + await this.getPublicState() + ).ShieldedAccessControl__operatorRoles.findPathForLeaf(roleCommitment); }, /** @@ -165,11 +176,19 @@ export class ShieldedAccessControlSimulator extends ShieldedAccessControlSimulat * @param roleCommitment - The role commitment to find a path for. * @returns The Merkle tree path as returned by the witness. */ - getCommitmentPathWithWitnessImpl: ( + getCommitmentPathWithWitnessImpl: async ( roleCommitment: Uint8Array, - ): MerkleTreePath => { + ): Promise> => { + const context: WitnessContext< + ShieldedAccessControlLedger, + ShieldedAccessControlPrivateState + > = { + ledger: await this.getPublicState(), + privateState: await this.getPrivateState(), + contractAddress: '', + }; return this.witnesses.wit_getRoleCommitmentPath( - this.getWitnessContext(), + context, roleCommitment, )[1]; }, diff --git a/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts b/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts index 05ddf6ec..c0c780d3 100644 --- a/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts +++ b/contracts/src/access/test/simulators/ZOwnablePKSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -49,22 +49,27 @@ const ZOwnablePKSimulatorBase: any = createSimulator< }, ledgerExtractor: (state) => ledger(state), witnessesFactory: () => ZOwnablePKWitnesses(), + artifactName: 'MockZOwnablePK', }); /** * ZOwnablePKSimulator */ export class ZOwnablePKSimulator extends ZOwnablePKSimulatorBase { - constructor( + static async create( ownerId: Uint8Array, instanceSalt: Uint8Array, isInit: boolean, - options: BaseSimulatorOptions< + options: SimulatorOptions< ZOwnablePKPrivateState, ReturnType > = {}, - ) { - super([ownerId, instanceSalt, isInit], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [ownerId, instanceSalt, isInit], + options, + ) as Promise; } /** @@ -72,7 +77,7 @@ export class ZOwnablePKSimulator extends ZOwnablePKSimulatorBase { * The full commitment is: `SHA256(SHA256(pk, nonce), instanceSalt, counter, domain)`. * @returns The current owner's commitment. */ - public owner(): Uint8Array { + public owner(): Promise { return this.circuits.impure.owner(); } @@ -81,8 +86,8 @@ export class ZOwnablePKSimulator extends ZOwnablePKSimulatorBase { * `newOwnerId` must be precalculated and given to the current owner off chain. * @param newOwnerId The new owner's unique identifier (`SHA256(pk, nonce)`). */ - public transferOwnership(newOwnerId: Uint8Array) { - this.circuits.impure.transferOwnership(newOwnerId); + public transferOwnership(newOwnerId: Uint8Array): Promise<[]> { + return this.circuits.impure.transferOwnership(newOwnerId); } /** @@ -90,16 +95,16 @@ export class ZOwnablePKSimulator extends ZOwnablePKSimulatorBase { * It will not be possible to call `assertOnlyOnwer` circuits anymore. * Can only be called by the current owner. */ - public renounceOwnership() { - this.circuits.impure.renounceOwnership(); + public renounceOwnership(): Promise<[]> { + return this.circuits.impure.renounceOwnership(); } /** * @description Throws if called by any account whose id hash `SHA256(pk, nonce)` does not match * the stored owner commitment. Use this to only allow the owner to call specific circuits. */ - public assertOnlyOwner() { - this.circuits.impure.assertOnlyOwner(); + public assertOnlyOwner(): Promise<[]> { + return this.circuits.impure.assertOnlyOwner(); } /** @@ -109,7 +114,10 @@ export class ZOwnablePKSimulator extends ZOwnablePKSimulatorBase { * after every transfer to prevent duplicate commitments given the same `id`. * @returns The commitment derived from `id` and `counter`. */ - public _computeOwnerCommitment(id: Uint8Array, counter: bigint): Uint8Array { + public _computeOwnerCommitment( + id: Uint8Array, + counter: bigint, + ): Promise { return this.circuits.impure._computeOwnerCommitment(id, counter); } @@ -123,7 +131,7 @@ export class ZOwnablePKSimulator extends ZOwnablePKSimulatorBase { public _computeOwnerId( pk: Either, nonce: Uint8Array, - ): Uint8Array { + ): Promise { return this.circuits.pure._computeOwnerId(pk, nonce); } @@ -132,8 +140,8 @@ export class ZOwnablePKSimulator extends ZOwnablePKSimulatorBase { * enforcing permission checks on the caller. * @param newOwnerId - The unique identifier of the new owner calculated by `SHA256(pk, nonce)`. */ - public _transferOwnership(newOwnerId: Uint8Array) { - this.circuits.impure._transferOwnership(newOwnerId); + public _transferOwnership(newOwnerId: Uint8Array): Promise<[]> { + return this.circuits.impure._transferOwnership(newOwnerId); } public readonly privateState = { @@ -142,23 +150,21 @@ export class ZOwnablePKSimulator extends ZOwnablePKSimulatorBase { * @param newNonce The secret nonce. * @returns The ZOwnablePK private state after setting the new nonce. */ - injectSecretNonce: ( + injectSecretNonce: async ( newNonce: Buffer, - ): ZOwnablePKPrivateState => { - const currentState = - this.circuitContextManager.getContext().currentPrivateState; - const updatedState = { ...currentState, secretNonce: newNonce }; - this.circuitContextManager.updatePrivateState(updatedState); - return updatedState; + ): Promise => { + const cur = await this.getPrivateState(); + const updated = { ...cur, secretNonce: newNonce }; + this.setPrivateState(updated); + return updated; }, /** * @description Returns the secret nonce given the context. * @returns The secret nonce. */ - getCurrentSecretNonce: (): Uint8Array => { - return this.circuitContextManager.getContext().currentPrivateState - .secretNonce; + getCurrentSecretNonce: async (): Promise => { + return (await this.getPrivateState()).secretNonce; }, }; } diff --git a/contracts/src/archive/test/ShieldedToken.test.ts b/contracts/src/archive/test/ShieldedToken.test.ts index dac0b238..9fe8d4db 100644 --- a/contracts/src/archive/test/ShieldedToken.test.ts +++ b/contracts/src/archive/test/ShieldedToken.test.ts @@ -36,24 +36,39 @@ let thisTokenType: TokenType; describe('Shielded token', () => { describe('initializer and metadata', () => { - it('should initialize metadata', () => { - token = new ShieldedTokenSimulator(NONCE, NAME, SYMBOL, DECIMALS); + it('should initialize metadata', async () => { + token = await ShieldedTokenSimulator.create( + NONCE, + NAME, + SYMBOL, + DECIMALS, + ); - expect(token.name()).toEqual(NAME); - expect(token.symbol()).toEqual(SYMBOL); - expect(token.decimals()).toEqual(DECIMALS); + expect(await token.name()).toEqual(NAME); + expect(await token.symbol()).toEqual(SYMBOL); + expect(await token.decimals()).toEqual(DECIMALS); }); - it('should initialize empty metadata', () => { - token = new ShieldedTokenSimulator(NONCE, NO_STRING, NO_STRING, 0n); + it('should initialize empty metadata', async () => { + token = await ShieldedTokenSimulator.create( + NONCE, + NO_STRING, + NO_STRING, + 0n, + ); - expect(token.name()).toEqual(NO_STRING); - expect(token.symbol()).toEqual(NO_STRING); - expect(token.decimals()).toEqual(0n); + expect(await token.name()).toEqual(NO_STRING); + expect(await token.symbol()).toEqual(NO_STRING); + expect(await token.decimals()).toEqual(0n); }); - it('should set public state', () => { - token = new ShieldedTokenSimulator(NONCE, NAME, SYMBOL, DECIMALS); + it('should set public state', async () => { + token = await ShieldedTokenSimulator.create( + NONCE, + NAME, + SYMBOL, + DECIMALS, + ); expect(token.getCurrentPublicState().ShieldedToken__counter).toEqual(0n); expect(token.getCurrentPublicState().ShieldedToken__domain).toEqual( @@ -63,14 +78,14 @@ describe('Shielded token', () => { }); }); - beforeEach(() => { - token = new ShieldedTokenSimulator(NONCE, NAME, SYMBOL, DECIMALS); + beforeEach(async () => { + token = await ShieldedTokenSimulator.create(NONCE, NAME, SYMBOL, DECIMALS); thisTokenType = tokenType(DOMAIN, token.contractAddress); }); describe('mint', () => { - it('should mint', () => { - const res = token.mint(Z_OWNER, AMOUNT); + it('should mint', async () => { + const res = await token.mint(Z_OWNER, AMOUNT); const thisNonce = token.getCurrentPublicState().ShieldedToken__nonce; const thisCoinInfo = { color: encodeTokenType(thisTokenType), @@ -88,21 +103,21 @@ describe('Shielded token', () => { Z_OWNER, ); // Check supply - expect(token.totalSupply()).toEqual(AMOUNT); + expect(await token.totalSupply()).toEqual(AMOUNT); }); - it('should bump counter', () => { + it('should bump counter', async () => { expect(token.getCurrentPublicState().ShieldedToken__counter).toEqual(0n); - token.mint(Z_OWNER, AMOUNT); + await token.mint(Z_OWNER, AMOUNT); expect(token.getCurrentPublicState().ShieldedToken__counter).toEqual(1n); }); - it('should bump nonce', () => { + it('should bump nonce', async () => { const initNonce = token.getCurrentPublicState().ShieldedToken__nonce; expect(initNonce).toEqual(NONCE); - token.mint(Z_OWNER, AMOUNT); + await token.mint(Z_OWNER, AMOUNT); // TODO: create js equivalent of `evolve_nonce` circuit to derive correct value expect(initNonce).not.toEqual( @@ -110,27 +125,27 @@ describe('Shielded token', () => { ); }); - it('should fail when minting to the zero address', () => { - expect(() => { - token.mint(utils.ZERO_KEY, AMOUNT); - }).toThrow('ShieldedToken: invalid recipient'); + it('should fail when minting to the zero address', async () => { + await expect(token.mint(utils.ZERO_KEY, AMOUNT)).rejects.toThrow( + 'ShieldedToken: invalid recipient', + ); }); - it('should fail when minting overflow uint64', () => { - token.mint(Z_OWNER, MAX_UINT64); + it('should fail when minting overflow uint64', async () => { + await token.mint(Z_OWNER, MAX_UINT64); - expect(() => { - token.mint(Z_OWNER, 1n); - }).toThrow('arithmetic overflow'); + await expect(token.mint(Z_OWNER, 1n)).rejects.toThrow( + 'arithmetic overflow', + ); }); }); describe('burn', () => { - beforeEach(() => { - token.mint(Z_OWNER, AMOUNT); + beforeEach(async () => { + await token.mint(Z_OWNER, AMOUNT); }); - it('should burn (whole)', () => { + it('should burn (whole)', async () => { const nonceStr = NONCE.filter((x) => x !== 0) .join('') .padStart(64, '0'); //297481949006 @@ -142,7 +157,7 @@ describe('Shielded token', () => { const encoded_coin_info = encodeCoinInfo(coin_info); // Burn - const res = token.burn(encoded_coin_info, AMOUNT); + const res = await token.burn(encoded_coin_info, AMOUNT); // Check circuit result expect(res.result.change.is_some).toBe(false); @@ -162,10 +177,10 @@ describe('Shielded token', () => { expect(txInputs.nonce).toEqual(encoded_coin_info.nonce); // Check supply - expect(token.totalSupply()).toEqual(0n); + expect(await token.totalSupply()).toEqual(0n); }); - it('should burn (partial)', () => { + it('should burn (partial)', async () => { const nonceStr = NONCE.filter((x) => x !== 0) .join('') .padStart(64, '0'); @@ -178,7 +193,7 @@ describe('Shielded token', () => { const encoded_coin_info = encodeCoinInfo(coin_info); // Burn - const res = token.burn(encoded_coin_info, partialAmt); + const res = await token.burn(encoded_coin_info, partialAmt); // Check circuit result const change = res.result.change; @@ -204,10 +219,10 @@ describe('Shielded token', () => { expect(txInput2.nonce).not.toEqual(encoded_coin_info.nonce); // Check supply - expect(token.totalSupply()).toEqual(1n); + expect(await token.totalSupply()).toEqual(1n); }); - it('should fail with incorrect domain', () => { + it('should fail with incorrect domain', async () => { const nonceStr = NONCE.filter((x) => x !== 0) .join('') .padStart(64, '0'); @@ -220,12 +235,12 @@ describe('Shielded token', () => { }; const encoded_coin_info = encodeCoinInfo(coin_info); - expect(() => { - token.burn(encoded_coin_info, AMOUNT); - }).toThrow('ShieldedToken: token not created from this contract'); + await expect(token.burn(encoded_coin_info, AMOUNT)).rejects.toThrow( + 'ShieldedToken: token not created from this contract', + ); }); - it('should fail with incorrect address', () => { + it('should fail with incorrect address', async () => { const nonceStr = NONCE.filter((x) => x !== 0) .join('') .padStart(64, '0'); @@ -238,12 +253,12 @@ describe('Shielded token', () => { }; const encoded_coin_info = encodeCoinInfo(coin_info); - expect(() => { - token.burn(encoded_coin_info, AMOUNT); - }).toThrow('ShieldedToken: token not created from this contract'); + await expect(token.burn(encoded_coin_info, AMOUNT)).rejects.toThrow( + 'ShieldedToken: token not created from this contract', + ); }); - it('should fail when not enough balance', () => { + it('should fail when not enough balance', async () => { const nonceStr = NONCE.filter((x) => x !== 0) .join('') .padStart(64, '0'); @@ -255,9 +270,9 @@ describe('Shielded token', () => { }; const encoded_coin_info = encodeCoinInfo(coin_info); - expect(() => { - token.burn(encoded_coin_info, AMOUNT + 1n); - }).toThrow('ShieldedToken: insufficient token amount to burn'); + await expect(token.burn(encoded_coin_info, AMOUNT + 1n)).rejects.toThrow( + 'ShieldedToken: insufficient token amount to burn', + ); }); }); }); diff --git a/contracts/src/archive/test/simulators/ShieldedTokenSimulator.ts b/contracts/src/archive/test/simulators/ShieldedTokenSimulator.ts index da476a9d..85a33d6b 100644 --- a/contracts/src/archive/test/simulators/ShieldedTokenSimulator.ts +++ b/contracts/src/archive/test/simulators/ShieldedTokenSimulator.ts @@ -19,14 +19,24 @@ import { type SendResult, type ZswapCoinPublicKey, } from '../../../../artifacts/MockShieldedToken/contract/index.js'; // Combined imports +import type { IContractSimulator } from '../types/test.js'; import { type ShieldedTokenPrivateState, ShieldedTokenWitnesses, } from '../witnesses/ShieldedTokenWitnesses.js'; -import type { IContractSimulator } from '../types/test.js'; /** * @description A simulator implementation of a shielded token contract for testing purposes. + * + * @remarks + * The `archive` module predates the backend-aware `@openzeppelin/compact-simulator` + * factory (`createSimulator`). Its tests inspect the raw `CircuitResults` + * (`res.context.currentZswapLocalState` inputs/outputs) and override the Zswap + * sender per call, neither of which the async circuit proxy surfaces. The + * simulator therefore keeps its hand-rolled `IContractSimulator` shape, but its + * lifecycle is aligned with the async API: construction goes through + * `await ShieldedTokenSimulator.create(...)` and every circuit method is awaited. + * * @template P - The private state type, fixed to ShieldedTokenPrivateState. * @template L - The ledger type, fixed to Contract.Ledger. */ @@ -45,27 +55,40 @@ export class ShieldedTokenSimulator /** * @description Initializes the mock contract. */ - constructor( + private constructor( + contract: MockShielded, + circuitContext: CircuitContext, + ) { + this.contract = contract; + this.circuitContext = circuitContext; + this.contractAddress = this.circuitContext.transactionContext.address; + } + + /** + * @description Constructs a simulator, deploying the mock contract to fresh + * in-memory state. + */ + static async create( nonce: Uint8Array, name: Maybe, symbol: Maybe, decimals: bigint, - ) { - this.contract = new MockShielded( + ): Promise { + const contract = new MockShielded( ShieldedTokenWitnesses, ); const { currentPrivateState, currentContractState, currentZswapLocalState, - } = this.contract.initialState( + } = contract.initialState( createConstructorContext({}, '0'.repeat(64)), nonce, name, symbol, decimals, ); - this.circuitContext = { + const circuitContext: CircuitContext = { currentPrivateState, currentZswapLocalState, originalState: currentContractState, @@ -74,7 +97,7 @@ export class ShieldedTokenSimulator sampleContractAddress(), ), }; - this.contractAddress = this.circuitContext.transactionContext.address; + return new ShieldedTokenSimulator(contract, circuitContext); } /** @@ -105,7 +128,7 @@ export class ShieldedTokenSimulator * @description Returns the token name. * @returns The token name. */ - public name(): Maybe { + public async name(): Promise> { return this.contract.impureCircuits.name(this.circuitContext).result; } @@ -113,7 +136,7 @@ export class ShieldedTokenSimulator * @description Returns the symbol of the token. * @returns The token name. */ - public symbol(): Maybe { + public async symbol(): Promise> { return this.contract.impureCircuits.symbol(this.circuitContext).result; } @@ -121,7 +144,7 @@ export class ShieldedTokenSimulator * @description Returns the number of decimals used to get its user representation. * @returns The account's token balance. */ - public decimals(): bigint { + public async decimals(): Promise { return this.contract.impureCircuits.decimals(this.circuitContext).result; } @@ -129,15 +152,15 @@ export class ShieldedTokenSimulator * @description Returns the value of tokens in existence. * @returns The total supply of tokens. */ - public totalSupply(): bigint { + public async totalSupply(): Promise { return this.contract.impureCircuits.totalSupply(this.circuitContext).result; } - public mint( + public async mint( recipient: Either, amount: bigint, sender?: CoinPublicKey, - ): CircuitResults { + ): Promise> { const res = this.contract.impureCircuits.mint( { ...this.circuitContext, @@ -153,11 +176,11 @@ export class ShieldedTokenSimulator return res; } - public burn( + public async burn( coin: CoinInfo, amount: bigint, sender?: CoinPublicKey, - ): CircuitResults { + ): Promise> { const res = this.contract.impureCircuits.burn( { ...this.circuitContext, diff --git a/contracts/src/multisig/test/Forwarder.test.ts b/contracts/src/multisig/test/Forwarder.test.ts index 931c3432..e9fa63eb 100644 --- a/contracts/src/multisig/test/Forwarder.test.ts +++ b/contracts/src/multisig/test/Forwarder.test.ts @@ -28,87 +28,92 @@ function makeCoin(color: Uint8Array, value: bigint, nonce?: Uint8Array) { describe('ForwarderShielded module', () => { describe('initialization', () => { - it('should initialize on construction when isInit is true', () => { - expect( - () => new MockForwarderShieldedSimulator(SHIELDED_PARENT, true), - ).not.toThrow(); + it('should initialize on construction when isInit is true', async () => { + await MockForwarderShieldedSimulator.create(SHIELDED_PARENT, true); }); - it('should fail initialization with a zero parent', () => { - expect( - () => new MockForwarderShieldedSimulator(SHIELDED_ZERO, true), - ).toThrow('ForwarderShielded: zero parent'); + it('should fail initialization with a zero parent', async () => { + await expect( + MockForwarderShieldedSimulator.create(SHIELDED_ZERO, true), + ).rejects.toThrow('ForwarderShielded: zero parent'); }); - it('should store the coin-public-key parent in the left arm', () => { - const mock = new MockForwarderShieldedSimulator(SHIELDED_PARENT, true); - const parent = mock.getParent(); + it('should store the coin-public-key parent in the left arm', async () => { + const mock = await MockForwarderShieldedSimulator.create( + SHIELDED_PARENT, + true, + ); + const parent = await mock.getParent(); expect(parent.is_left).toBe(true); expect(parent.left).toEqual(SHIELDED_PARENT); }); }); describe('init guard', () => { - it('should fail deposit when not initialized', () => { - const mock = new MockForwarderShieldedSimulator(SHIELDED_PARENT, false); - expect(() => mock.deposit(makeCoin(COLOR, AMOUNT))).toThrow( + it('should fail deposit when not initialized', async () => { + const mock = await MockForwarderShieldedSimulator.create( + SHIELDED_PARENT, + false, + ); + await expect(mock.deposit(makeCoin(COLOR, AMOUNT))).rejects.toThrow( 'ForwarderShielded: contract not initialized', ); }); }); describe('deposit', () => { - it('should accept a shielded deposit and forward it', () => { - const mock = new MockForwarderShieldedSimulator(SHIELDED_PARENT, true); - expect(() => mock.deposit(makeCoin(COLOR, AMOUNT))).not.toThrow(); + it('should accept a shielded deposit and forward it', async () => { + const mock = await MockForwarderShieldedSimulator.create( + SHIELDED_PARENT, + true, + ); + await mock.deposit(makeCoin(COLOR, AMOUNT)); }); }); }); describe('ForwarderUnshielded module', () => { describe('initialization', () => { - it('should initialize on construction when isInit is true', () => { - expect( - () => new MockForwarderUnshieldedSimulator(UNSHIELDED_PARENT, true), - ).not.toThrow(); + it('should initialize on construction when isInit is true', async () => { + await MockForwarderUnshieldedSimulator.create(UNSHIELDED_PARENT, true); }); - it('should fail initialization with a zero parent', () => { - expect( - () => new MockForwarderUnshieldedSimulator(UNSHIELDED_ZERO, true), - ).toThrow('ForwarderUnshielded: zero parent'); + it('should fail initialization with a zero parent', async () => { + await expect( + MockForwarderUnshieldedSimulator.create(UNSHIELDED_ZERO, true), + ).rejects.toThrow('ForwarderUnshielded: zero parent'); }); - it('should store the user-address parent in the right arm', () => { - const mock = new MockForwarderUnshieldedSimulator( + it('should store the user-address parent in the right arm', async () => { + const mock = await MockForwarderUnshieldedSimulator.create( UNSHIELDED_PARENT, true, ); - const parent = mock.getParent(); + const parent = await mock.getParent(); expect(parent.is_left).toBe(false); expect(parent.right).toEqual(UNSHIELDED_PARENT); }); }); describe('init guard', () => { - it('should fail deposit when not initialized', () => { - const mock = new MockForwarderUnshieldedSimulator( + it('should fail deposit when not initialized', async () => { + const mock = await MockForwarderUnshieldedSimulator.create( UNSHIELDED_PARENT, false, ); - expect(() => mock.deposit(COLOR, AMOUNT)).toThrow( + await expect(mock.deposit(COLOR, AMOUNT)).rejects.toThrow( 'ForwarderUnshielded: contract not initialized', ); }); }); describe('deposit', () => { - it('should accept an unshielded deposit and forward it', () => { - const mock = new MockForwarderUnshieldedSimulator( + it('should accept an unshielded deposit and forward it', async () => { + const mock = await MockForwarderUnshieldedSimulator.create( UNSHIELDED_PARENT, true, ); - expect(() => mock.deposit(COLOR, AMOUNT)).not.toThrow(); + await mock.deposit(COLOR, AMOUNT); }); }); }); diff --git a/contracts/src/multisig/test/ForwarderPrivate.test.ts b/contracts/src/multisig/test/ForwarderPrivate.test.ts index 8b890195..92ffc2e6 100644 --- a/contracts/src/multisig/test/ForwarderPrivate.test.ts +++ b/contracts/src/multisig/test/ForwarderPrivate.test.ts @@ -52,66 +52,66 @@ function commitment(parent: Uint8Array, opSecret: Uint8Array): Uint8Array { } /** Initialized forwarder committed to `committedBytes`, with one coin deposited. */ -function freshMock( +async function freshMock( committedBytes: Uint8Array, opSecret: Uint8Array = OP_SECRET, -): MockForwarderPrivateSimulator { - const mock = new MockForwarderPrivateSimulator( +): Promise { + const mock = await MockForwarderPrivateSimulator.create( commitment(committedBytes, opSecret), true, ); - mock.deposit(makeCoin(COLOR, AMOUNT)); + await mock.deposit(makeCoin(COLOR, AMOUNT)); return mock; } describe('ForwarderPrivate module', () => { describe('initialization', () => { - it('should initialize on construction when isInit is true', () => { + it('should initialize on construction when isInit is true', async () => { const c = commitment(PARENT_BYTES, OP_SECRET); - const mock = new MockForwarderPrivateSimulator(c, true); - expect(() => mock.deposit(makeCoin(COLOR, AMOUNT))).not.toThrow(); + const mock = await MockForwarderPrivateSimulator.create(c, true); + await mock.deposit(makeCoin(COLOR, AMOUNT)); }); - it('should fail initialization with zero commitment', () => { - expect(() => new MockForwarderPrivateSimulator(ZERO, true)).toThrow( - 'ForwarderPrivate: zero commitment', - ); + it('should fail initialization with zero commitment', async () => { + await expect( + MockForwarderPrivateSimulator.create(ZERO, true), + ).rejects.toThrow('ForwarderPrivate: zero commitment'); }); - it('should store the parent commitment after initialization', () => { + it('should store the parent commitment after initialization', async () => { const c = commitment(PARENT_BYTES, OP_SECRET); - const mock = new MockForwarderPrivateSimulator(c, true); + const mock = await MockForwarderPrivateSimulator.create(c, true); // The module is imported with a prefix only, so `_parentCommitment` is // not in the public ledger reader; read it via the getter circuit. - expect(mock.getParentCommitment()).toStrictEqual(c); + expect(await mock.getParentCommitment()).toStrictEqual(c); }); }); describe('init guard', () => { let mock: MockForwarderPrivateSimulator; - beforeEach(() => { - mock = new MockForwarderPrivateSimulator( + beforeEach(async () => { + mock = await MockForwarderPrivateSimulator.create( commitment(PARENT_BYTES, OP_SECRET), false, ); }); - it('should fail deposit when not initialized', () => { - expect(() => mock.deposit(makeCoin(COLOR, AMOUNT))).toThrow( + it('should fail deposit when not initialized', async () => { + await expect(mock.deposit(makeCoin(COLOR, AMOUNT))).rejects.toThrow( 'ForwarderPrivate: contract not initialized', ); }); - it('should fail drain when not initialized', () => { - expect(() => + it('should fail drain when not initialized', async () => { + await expect( mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(PARENT_BYTES), OP_SECRET, AMOUNT, ), - ).toThrow('ForwarderPrivate: contract not initialized'); + ).rejects.toThrow('ForwarderPrivate: contract not initialized'); }); }); @@ -145,12 +145,12 @@ describe('ForwarderPrivate module', () => { describe('drain', () => { let mock: MockForwarderPrivateSimulator; - beforeEach(() => { - mock = freshMock(PARENT_BYTES); + beforeEach(async () => { + mock = await freshMock(PARENT_BYTES); }); - it('should succeed drain with correct (parent, opSecret)', () => { - const result = mock.drain( + it('should succeed drain with correct (parent, opSecret)', async () => { + const result = await mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(PARENT_BYTES), OP_SECRET, @@ -159,52 +159,52 @@ describe('ForwarderPrivate module', () => { expect(result.sent.value).toEqual(AMOUNT); }); - it('should fail drain with wrong parent key', () => { - expect(() => + it('should fail drain with wrong parent key', async () => { + await expect( mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(WRONG_BYTES), OP_SECRET, AMOUNT, ), - ).toThrow('ForwarderPrivate: invalid parent'); + ).rejects.toThrow('ForwarderPrivate: invalid parent'); }); - it('should fail drain with wrong opSecret', () => { - expect(() => + it('should fail drain with wrong opSecret', async () => { + await expect( mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(PARENT_BYTES), WRONG_OP_SECRET, AMOUNT, ), - ).toThrow('ForwarderPrivate: invalid parent'); + ).rejects.toThrow('ForwarderPrivate: invalid parent'); }); - it('should fail drain with both wrong', () => { - expect(() => + it('should fail drain with both wrong', async () => { + await expect( mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(WRONG_BYTES), WRONG_OP_SECRET, AMOUNT, ), - ).toThrow('ForwarderPrivate: invalid parent'); + ).rejects.toThrow('ForwarderPrivate: invalid parent'); }); - it('should fail drain with value > coin.value', () => { - expect(() => + it('should fail drain with value > coin.value', async () => { + await expect( mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(PARENT_BYTES), OP_SECRET, AMOUNT + 1n, ), - ).toThrow(); + ).rejects.toThrow(); }); - it('should produce no change when drain value equals coin value', () => { - const result = mock.drain( + it('should produce no change when drain value equals coin value', async () => { + const result = await mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(PARENT_BYTES), OP_SECRET, @@ -213,8 +213,8 @@ describe('ForwarderPrivate module', () => { expect(result.change.is_some).toBe(false); }); - it('should produce a change coin when drain value is less than coin value', () => { - const result = mock.drain( + it('should produce a change coin when drain value is less than coin value', async () => { + const result = await mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(PARENT_BYTES), OP_SECRET, @@ -225,8 +225,8 @@ describe('ForwarderPrivate module', () => { expect(result.change.value.color).toEqual(COLOR); }); - it('should produce a sent coin of exactly value on partial drain', () => { - const result = mock.drain( + it('should produce a sent coin of exactly value on partial drain', async () => { + const result = await mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(PARENT_BYTES), OP_SECRET, @@ -239,16 +239,16 @@ describe('ForwarderPrivate module', () => { // INV-34: a zero parent key is rejected before the commitment gate. describe('drain — rejects a zero parent', () => { - it('should reject a zero parent key', () => { - const mock = freshMock(PARENT_BYTES); - expect(() => + it('should reject a zero parent key', async () => { + const mock = await freshMock(PARENT_BYTES); + await expect( mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(ZERO), OP_SECRET, AMOUNT, ), - ).toThrow('ForwarderPrivate: zero parent'); + ).rejects.toThrow('ForwarderPrivate: zero parent'); }); }); @@ -263,19 +263,19 @@ describe('ForwarderPrivate module', () => { // coin-public-key recipient occurs 0 times in the published tx); not // simulator-observable, so it is asserted by the residual-surface check here. describe('drain — residual public surface (INV-12 / INV-17 / INV-25)', () => { - it('should not mutate the parent commitment on a successful drain', () => { + it('should not mutate the parent commitment on a successful drain', async () => { const c = commitment(PARENT_BYTES, OP_SECRET); - const mock = new MockForwarderPrivateSimulator(c, true); - mock.deposit(makeCoin(COLOR, AMOUNT)); + const mock = await MockForwarderPrivateSimulator.create(c, true); + await mock.deposit(makeCoin(COLOR, AMOUNT)); - const before = mock.getParentCommitment(); - mock.drain( + const before = await mock.getParentCommitment(); + await mock.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(PARENT_BYTES), OP_SECRET, AMOUNT, ); - const after = mock.getParentCommitment(); + const after = await mock.getParentCommitment(); expect(after).toStrictEqual(before); expect(after).toStrictEqual(c); @@ -283,19 +283,19 @@ describe('ForwarderPrivate module', () => { }); describe('property: change arithmetic', () => { - it('should preserve change.value == coin.value - drain.value on partial drain', () => { - fc.assert( - fc.property( + it('should preserve change.value == coin.value - drain.value on partial drain', async () => { + await fc.assert( + fc.asyncProperty( fc.bigInt({ min: 2n, max: MAX_U64 - 1n }), fc.bigInt({ min: 1n, max: MAX_U64 - 1n }), - (coinVal, drainVal) => { + async (coinVal, drainVal) => { fc.pre(drainVal < coinVal); - const mock = new MockForwarderPrivateSimulator( + const mock = await MockForwarderPrivateSimulator.create( commitment(PARENT_BYTES, OP_SECRET), true, ); - mock.deposit(makeCoin(COLOR, coinVal)); - const result = mock.drain( + await mock.deposit(makeCoin(COLOR, coinVal)); + const result = await mock.drain( makeQualifiedCoin(COLOR, coinVal, 0n), key(PARENT_BYTES), OP_SECRET, diff --git a/contracts/src/multisig/test/ProposalManager.test.ts b/contracts/src/multisig/test/ProposalManager.test.ts index 42b35da8..b93c0d48 100644 --- a/contracts/src/multisig/test/ProposalManager.test.ts +++ b/contracts/src/multisig/test/ProposalManager.test.ts @@ -17,8 +17,8 @@ const Z_CONTRACT_RECIPIENT = utils.encodeToAddress('CONTRACT_RECIPIENT'); let contract: ProposalManagerSimulator; describe('ProposalManager', () => { - beforeEach(() => { - contract = new ProposalManagerSimulator(); + beforeEach(async () => { + contract = await ProposalManagerSimulator.create(); }); describe('recipient helpers (pure)', () => { @@ -89,25 +89,25 @@ describe('ProposalManager', () => { }); describe('_createProposal', () => { - it('should create a proposal and return id', () => { + it('should create a proposal and return id', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); expect(id).toEqual(1n); }); - it('should create sequential proposal ids', () => { + it('should create sequential proposal ids', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id1 = contract._createProposal(recipient, COLOR, AMOUNT); - const id2 = contract._createProposal(recipient, COLOR2, AMOUNT2); + const id1 = await contract._createProposal(recipient, COLOR, AMOUNT); + const id2 = await contract._createProposal(recipient, COLOR2, AMOUNT2); expect(id1).toEqual(1n); expect(id2).toEqual(2n); }); - it('should store proposal data correctly', () => { + it('should store proposal data correctly', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); - const proposal = contract.getProposal(id); + const proposal = await contract.getProposal(id); expect(proposal.to.kind).toEqual(RecipientKind.ShieldedUser); expect(proposal.to.address).toEqual(Z_RECIPIENT.bytes); expect(proposal.color).toEqual(COLOR); @@ -115,241 +115,259 @@ describe('ProposalManager', () => { expect(proposal.status).toEqual(ProposalStatus.Active); }); - it('should store contract recipient correctly', () => { + it('should store contract recipient correctly', async () => { const recipient = contract.contractRecipient(Z_CONTRACT_RECIPIENT); - const id = contract._createProposal(recipient, COLOR2, AMOUNT2); + const id = await contract._createProposal(recipient, COLOR2, AMOUNT2); - const proposal = contract.getProposal(id); + const proposal = await contract.getProposal(id); expect(proposal.to.kind).toEqual(RecipientKind.Contract); expect(proposal.to.address).toEqual(Z_CONTRACT_RECIPIENT.bytes); expect(proposal.color).toEqual(COLOR2); expect(proposal.amount).toEqual(AMOUNT2); }); - it('should fail with zero amount', () => { + it('should fail with zero amount', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - expect(() => { - contract._createProposal(recipient, COLOR, 0n); - }).toThrow('ProposalManager: zero amount'); + await expect( + contract._createProposal(recipient, COLOR, 0n), + ).rejects.toThrow('ProposalManager: zero amount'); }); }); describe('assertProposalExists', () => { - it('should pass for existing proposal', () => { + it('should pass for existing proposal', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - expect(() => contract.assertProposalExists(id)).not.toThrow(); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + await contract.assertProposalExists(id); }); - it('should fail for non-existing proposal', () => { - expect(() => { - contract.assertProposalExists(999n); - }).toThrow('ProposalManager: proposal not found'); + it('should fail for non-existing proposal', async () => { + await expect(contract.assertProposalExists(999n)).rejects.toThrow( + 'ProposalManager: proposal not found', + ); }); }); describe('assertProposalActive', () => { - it('should pass for active proposal', () => { + it('should pass for active proposal', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - expect(() => contract.assertProposalActive(id)).not.toThrow(); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + await contract.assertProposalActive(id); }); - it('should fail for non-existing proposal', () => { - expect(() => { - contract.assertProposalActive(999n); - }).toThrow('ProposalManager: proposal not found'); + it('should fail for non-existing proposal', async () => { + await expect(contract.assertProposalActive(999n)).rejects.toThrow( + 'ProposalManager: proposal not found', + ); }); - it('should fail for cancelled proposal', () => { + it('should fail for cancelled proposal', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - contract._cancelProposal(id); - expect(() => { - contract.assertProposalActive(id); - }).toThrow('ProposalManager: proposal not active'); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + await contract._cancelProposal(id); + await expect(contract.assertProposalActive(id)).rejects.toThrow( + 'ProposalManager: proposal not active', + ); }); - it('should fail for executed proposal', () => { + it('should fail for executed proposal', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - contract._markExecuted(id); - expect(() => { - contract.assertProposalActive(id); - }).toThrow('ProposalManager: proposal not active'); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + await contract._markExecuted(id); + await expect(contract.assertProposalActive(id)).rejects.toThrow( + 'ProposalManager: proposal not active', + ); }); }); describe('_cancelProposal', () => { - it('should cancel an active proposal', () => { + it('should cancel an active proposal', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); - contract._cancelProposal(id); - expect(contract.getProposalStatus(id)).toEqual(ProposalStatus.Cancelled); + await contract._cancelProposal(id); + expect(await contract.getProposalStatus(id)).toEqual( + ProposalStatus.Cancelled, + ); }); - it('should preserve proposal data after cancellation', () => { + it('should preserve proposal data after cancellation', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); - contract._cancelProposal(id); - const proposal = contract.getProposal(id); + await contract._cancelProposal(id); + const proposal = await contract.getProposal(id); expect(proposal.to.address).toEqual(Z_RECIPIENT.bytes); expect(proposal.color).toEqual(COLOR); expect(proposal.amount).toEqual(AMOUNT); }); - it('should fail for non-existing proposal', () => { - expect(() => { - contract._cancelProposal(999n); - }).toThrow('ProposalManager: proposal not found'); + it('should fail for non-existing proposal', async () => { + await expect(contract._cancelProposal(999n)).rejects.toThrow( + 'ProposalManager: proposal not found', + ); }); - it('should fail for already cancelled proposal', () => { + it('should fail for already cancelled proposal', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - contract._cancelProposal(id); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + await contract._cancelProposal(id); - expect(() => { - contract._cancelProposal(id); - }).toThrow('ProposalManager: proposal not active'); + await expect(contract._cancelProposal(id)).rejects.toThrow( + 'ProposalManager: proposal not active', + ); }); - it('should fail for executed proposal', () => { + it('should fail for executed proposal', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - contract._markExecuted(id); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + await contract._markExecuted(id); - expect(() => { - contract._cancelProposal(id); - }).toThrow('ProposalManager: proposal not active'); + await expect(contract._cancelProposal(id)).rejects.toThrow( + 'ProposalManager: proposal not active', + ); }); }); describe('_markExecuted', () => { - it('should mark an active proposal as executed', () => { + it('should mark an active proposal as executed', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); - contract._markExecuted(id); - expect(contract.getProposalStatus(id)).toEqual(ProposalStatus.Executed); + await contract._markExecuted(id); + expect(await contract.getProposalStatus(id)).toEqual( + ProposalStatus.Executed, + ); }); - it('should fail for non-existing proposal', () => { - expect(() => { - contract._markExecuted(999n); - }).toThrow('ProposalManager: proposal not found'); + it('should fail for non-existing proposal', async () => { + await expect(contract._markExecuted(999n)).rejects.toThrow( + 'ProposalManager: proposal not found', + ); }); - it('should fail for already executed proposal', () => { + it('should fail for already executed proposal', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - contract._markExecuted(id); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + await contract._markExecuted(id); - expect(() => { - contract._markExecuted(id); - }).toThrow('ProposalManager: proposal not active'); + await expect(contract._markExecuted(id)).rejects.toThrow( + 'ProposalManager: proposal not active', + ); }); - it('should fail for cancelled proposal', () => { + it('should fail for cancelled proposal', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - contract._cancelProposal(id); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + await contract._cancelProposal(id); - expect(() => { - contract._markExecuted(id); - }).toThrow('ProposalManager: proposal not active'); + await expect(contract._markExecuted(id)).rejects.toThrow( + 'ProposalManager: proposal not active', + ); }); }); describe('view circuits', () => { let proposalId: bigint; - beforeEach(() => { + beforeEach(async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - proposalId = contract._createProposal(recipient, COLOR, AMOUNT); + proposalId = await contract._createProposal(recipient, COLOR, AMOUNT); }); - it('getProposal should return full proposal', () => { - const proposal = contract.getProposal(proposalId); + it('getProposal should return full proposal', async () => { + const proposal = await contract.getProposal(proposalId); expect(proposal.to.kind).toEqual(RecipientKind.ShieldedUser); expect(proposal.color).toEqual(COLOR); expect(proposal.amount).toEqual(AMOUNT); expect(proposal.status).toEqual(ProposalStatus.Active); }); - it('getProposalRecipient should return recipient', () => { - const recipient = contract.getProposalRecipient(proposalId); + it('getProposalRecipient should return recipient', async () => { + const recipient = await contract.getProposalRecipient(proposalId); expect(recipient.kind).toEqual(RecipientKind.ShieldedUser); expect(recipient.address).toEqual(Z_RECIPIENT.bytes); }); - it('getProposalAmount should return amount', () => { - expect(contract.getProposalAmount(proposalId)).toEqual(AMOUNT); + it('getProposalAmount should return amount', async () => { + expect(await contract.getProposalAmount(proposalId)).toEqual(AMOUNT); }); - it('getProposalColor should return color', () => { - expect(contract.getProposalColor(proposalId)).toEqual(COLOR); + it('getProposalColor should return color', async () => { + expect(await contract.getProposalColor(proposalId)).toEqual(COLOR); }); - it('getProposalStatus should return status', () => { - expect(contract.getProposalStatus(proposalId)).toEqual( + it('getProposalStatus should return status', async () => { + expect(await contract.getProposalStatus(proposalId)).toEqual( ProposalStatus.Active, ); }); - it('all view circuits should fail for non-existing proposal', () => { + it('all view circuits should fail for non-existing proposal', async () => { const badId = 999n; - expect(() => contract.getProposal(badId)).toThrow( + await expect(contract.getProposal(badId)).rejects.toThrow( 'ProposalManager: proposal not found', ); - expect(() => contract.getProposalRecipient(badId)).toThrow( + await expect(contract.getProposalRecipient(badId)).rejects.toThrow( 'ProposalManager: proposal not found', ); - expect(() => contract.getProposalAmount(badId)).toThrow( + await expect(contract.getProposalAmount(badId)).rejects.toThrow( 'ProposalManager: proposal not found', ); - expect(() => contract.getProposalColor(badId)).toThrow( + await expect(contract.getProposalColor(badId)).rejects.toThrow( 'ProposalManager: proposal not found', ); - expect(() => contract.getProposalStatus(badId)).toThrow( + await expect(contract.getProposalStatus(badId)).rejects.toThrow( 'ProposalManager: proposal not found', ); }); }); describe('lifecycle transitions', () => { - it('should handle create -> cancel flow', () => { + it('should handle create -> cancel flow', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - expect(contract.getProposalStatus(id)).toEqual(ProposalStatus.Active); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + expect(await contract.getProposalStatus(id)).toEqual( + ProposalStatus.Active, + ); - contract._cancelProposal(id); - expect(contract.getProposalStatus(id)).toEqual(ProposalStatus.Cancelled); + await contract._cancelProposal(id); + expect(await contract.getProposalStatus(id)).toEqual( + ProposalStatus.Cancelled, + ); }); - it('should handle create -> execute flow', () => { + it('should handle create -> execute flow', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id = contract._createProposal(recipient, COLOR, AMOUNT); - expect(contract.getProposalStatus(id)).toEqual(ProposalStatus.Active); + const id = await contract._createProposal(recipient, COLOR, AMOUNT); + expect(await contract.getProposalStatus(id)).toEqual( + ProposalStatus.Active, + ); - contract._markExecuted(id); - expect(contract.getProposalStatus(id)).toEqual(ProposalStatus.Executed); + await contract._markExecuted(id); + expect(await contract.getProposalStatus(id)).toEqual( + ProposalStatus.Executed, + ); }); - it('should handle multiple proposals independently', () => { + it('should handle multiple proposals independently', async () => { const recipient = contract.shieldedUserRecipient(Z_RECIPIENT); - const id1 = contract._createProposal(recipient, COLOR, AMOUNT); - const id2 = contract._createProposal(recipient, COLOR2, AMOUNT2); + const id1 = await contract._createProposal(recipient, COLOR, AMOUNT); + const id2 = await contract._createProposal(recipient, COLOR2, AMOUNT2); - contract._cancelProposal(id1); + await contract._cancelProposal(id1); - expect(contract.getProposalStatus(id1)).toEqual(ProposalStatus.Cancelled); - expect(contract.getProposalStatus(id2)).toEqual(ProposalStatus.Active); + expect(await contract.getProposalStatus(id1)).toEqual( + ProposalStatus.Cancelled, + ); + expect(await contract.getProposalStatus(id2)).toEqual( + ProposalStatus.Active, + ); - contract._markExecuted(id2); - expect(contract.getProposalStatus(id2)).toEqual(ProposalStatus.Executed); + await contract._markExecuted(id2); + expect(await contract.getProposalStatus(id2)).toEqual( + ProposalStatus.Executed, + ); }); }); }); diff --git a/contracts/src/multisig/test/ShieldedMultiSig.test.ts b/contracts/src/multisig/test/ShieldedMultiSig.test.ts index 6fb97363..d1ee95b8 100644 --- a/contracts/src/multisig/test/ShieldedMultiSig.test.ts +++ b/contracts/src/multisig/test/ShieldedMultiSig.test.ts @@ -10,12 +10,12 @@ const COLOR = new Uint8Array(32).fill(1); const AMOUNT = 1000n; const PROPOSAL_AMOUNT = 400n; -const [SIGNER1, Z_SIGNER1] = utils.generateEitherPubKeyPair('SIGNER1'); -const [SIGNER2, Z_SIGNER2] = utils.generateEitherPubKeyPair('SIGNER2'); -const [SIGNER3, Z_SIGNER3] = utils.generateEitherPubKeyPair('SIGNER3'); +const [, Z_SIGNER1] = utils.generateEitherPubKeyPair('SIGNER1'); +const [, Z_SIGNER2] = utils.generateEitherPubKeyPair('SIGNER2'); +const [, Z_SIGNER3] = utils.generateEitherPubKeyPair('SIGNER3'); const SIGNERS = [Z_SIGNER1, Z_SIGNER2, Z_SIGNER3]; -const [_NON_SIGNER, Z_NON_SIGNER] = utils.generateEitherPubKeyPair('OTHER'); +const [, Z_NON_SIGNER] = utils.generateEitherPubKeyPair('OTHER'); const [, Z_RECIPIENT_PK] = utils.generatePubKeyPair('RECIPIENT'); function makeRecipient(pk: { bytes: Uint8Array }): { @@ -41,121 +41,125 @@ let multisig: ShieldedMultiSigSimulator; describe('ShieldedMultiSig', () => { describe('constructor', () => { - it('should initialize with signers and threshold', () => { - multisig = new ShieldedMultiSigSimulator(SIGNERS, THRESHOLD); - expect(multisig.getSignerCount()).toEqual(BigInt(SIGNERS.length)); - expect(multisig.getThreshold()).toEqual(THRESHOLD); + it('should initialize with signers and threshold', async () => { + multisig = await ShieldedMultiSigSimulator.create(SIGNERS, THRESHOLD); + expect(await multisig.getSignerCount()).toEqual(BigInt(SIGNERS.length)); + expect(await multisig.getThreshold()).toEqual(THRESHOLD); }); - it('should register all signers', () => { - multisig = new ShieldedMultiSigSimulator(SIGNERS, THRESHOLD); + it('should register all signers', async () => { + multisig = await ShieldedMultiSigSimulator.create(SIGNERS, THRESHOLD); for (const signer of SIGNERS) { - expect(multisig.isSigner(signer)).toEqual(true); + expect(await multisig.isSigner(signer)).toEqual(true); } }); - it('should reject non-signers', () => { - multisig = new ShieldedMultiSigSimulator(SIGNERS, THRESHOLD); - expect(multisig.isSigner(Z_NON_SIGNER)).toEqual(false); + it('should reject non-signers', async () => { + multisig = await ShieldedMultiSigSimulator.create(SIGNERS, THRESHOLD); + expect(await multisig.isSigner(Z_NON_SIGNER)).toEqual(false); }); - it('should fail with zero threshold', () => { - expect(() => { - new ShieldedMultiSigSimulator(SIGNERS, 0n); - }).toThrow('SignerManager: threshold must be > 0'); + it('should fail with zero threshold', async () => { + await expect( + ShieldedMultiSigSimulator.create(SIGNERS, 0n), + ).rejects.toThrow('SignerManager: threshold must be > 0'); }); - it('should fail with threshold exceeding signer count', () => { - expect(() => { - new ShieldedMultiSigSimulator(SIGNERS, 4n); - }).toThrow('SignerManager: threshold exceeds signer count'); + it('should fail with threshold exceeding signer count', async () => { + await expect( + ShieldedMultiSigSimulator.create(SIGNERS, 4n), + ).rejects.toThrow('SignerManager: threshold exceeds signer count'); }); }); describe('when initialized', () => { - beforeEach(() => { - multisig = new ShieldedMultiSigSimulator(SIGNERS, THRESHOLD); + beforeEach(async () => { + multisig = await ShieldedMultiSigSimulator.create(SIGNERS, THRESHOLD); }); describe('deposit', () => { - it('should accept deposits', () => { - multisig.deposit(makeCoin(COLOR, AMOUNT)); - expect(multisig.getTokenBalance(COLOR)).toEqual(AMOUNT); + it('should accept deposits', async () => { + await multisig.deposit(makeCoin(COLOR, AMOUNT)); + expect(await multisig.getTokenBalance(COLOR)).toEqual(AMOUNT); }); - it('should accumulate deposits', () => { - multisig.deposit(makeCoin(COLOR, AMOUNT, new Uint8Array(32).fill(1))); - multisig.deposit(makeCoin(COLOR, AMOUNT, new Uint8Array(32).fill(2))); - expect(multisig.getTokenBalance(COLOR)).toEqual(AMOUNT * 2n); + it('should accumulate deposits', async () => { + await multisig.deposit( + makeCoin(COLOR, AMOUNT, new Uint8Array(32).fill(1)), + ); + await multisig.deposit( + makeCoin(COLOR, AMOUNT, new Uint8Array(32).fill(2)), + ); + expect(await multisig.getTokenBalance(COLOR)).toEqual(AMOUNT * 2n); }); - it('should track received total', () => { - multisig.deposit(makeCoin(COLOR, AMOUNT)); - expect(multisig.getReceivedTotal(COLOR)).toEqual(AMOUNT); + it('should track received total', async () => { + await multisig.deposit(makeCoin(COLOR, AMOUNT)); + expect(await multisig.getReceivedTotal(COLOR)).toEqual(AMOUNT); }); }); describe('createShieldedProposal', () => { - it('should allow signer to create proposal', () => { + it('should allow signer to create proposal', async () => { const to = makeRecipient(Z_RECIPIENT_PK); - const id = multisig - .as(SIGNER1) + const id = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); expect(id).toEqual(1n); }); - it('should store proposal data correctly', () => { + it('should store proposal data correctly', async () => { const to = makeRecipient(Z_RECIPIENT_PK); - const id = multisig - .as(SIGNER1) + const id = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); - const proposal = multisig.getProposal(id); + const proposal = await multisig.getProposal(id); expect(proposal.status).toEqual(ProposalStatus.Active); expect(proposal.amount).toEqual(PROPOSAL_AMOUNT); expect(proposal.color).toEqual(COLOR); }); - it('should fail for non-signer', () => { + it('should fail for non-signer', async () => { const to = makeRecipient(Z_RECIPIENT_PK); - expect(() => { + await expect( multisig - .as(_NON_SIGNER) - .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); - }).toThrow('SignerManager: not a signer'); + .as('OTHER') + .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT), + ).rejects.toThrow('SignerManager: not a signer'); }); - it('should fail with zero amount', () => { + it('should fail with zero amount', async () => { const to = makeRecipient(Z_RECIPIENT_PK); - expect(() => { - multisig.as(SIGNER1).createShieldedProposal(to, COLOR, 0n); - }).toThrow('ProposalManager: zero amount'); + await expect( + multisig.as('SIGNER1').createShieldedProposal(to, COLOR, 0n), + ).rejects.toThrow('ProposalManager: zero amount'); }); - it('should reject UnshieldedUser recipient kind', () => { + it('should reject UnshieldedUser recipient kind', async () => { const to = { kind: RecipientKind.UnshieldedUser, address: Z_RECIPIENT_PK.bytes, }; - expect(() => { + await expect( multisig - .as(SIGNER1) - .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); - }).toThrow( + .as('SIGNER1') + .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT), + ).rejects.toThrow( 'ShieldedMultiSig: recipient must be a shielded user or contract', ); }); - it('should accept Contract recipient kind', () => { + it('should accept Contract recipient kind', async () => { const to = { kind: RecipientKind.Contract, address: new Uint8Array(32).fill(7), }; - const id = multisig - .as(SIGNER1) + const id = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); expect(id).toEqual(1n); - expect(multisig.getProposalRecipient(id).kind).toEqual( + expect((await multisig.getProposalRecipient(id)).kind).toEqual( RecipientKind.Contract, ); }); @@ -164,134 +168,134 @@ describe('ShieldedMultiSig', () => { describe('approveProposal', () => { let proposalId: bigint; - beforeEach(() => { + beforeEach(async () => { const to = makeRecipient(Z_RECIPIENT_PK); - proposalId = multisig - .as(SIGNER1) + proposalId = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); }); - it('should allow signer to approve', () => { - multisig.as(SIGNER1).approveProposal(proposalId); + it('should allow signer to approve', async () => { + await multisig.as('SIGNER1').approveProposal(proposalId); expect( - multisig.isProposalApprovedBySigner(proposalId, Z_SIGNER1), + await multisig.isProposalApprovedBySigner(proposalId, Z_SIGNER1), ).toEqual(true); - expect(multisig.getApprovalCount(proposalId)).toEqual(1n); + expect(await multisig.getApprovalCount(proposalId)).toEqual(1n); }); - it('should allow multiple signers to approve', () => { - multisig.as(SIGNER1).approveProposal(proposalId); - multisig.as(SIGNER2).approveProposal(proposalId); - expect(multisig.getApprovalCount(proposalId)).toEqual(2n); + it('should allow multiple signers to approve', async () => { + await multisig.as('SIGNER1').approveProposal(proposalId); + await multisig.as('SIGNER2').approveProposal(proposalId); + expect(await multisig.getApprovalCount(proposalId)).toEqual(2n); }); - it('should fail for non-signer', () => { - expect(() => { - multisig.as(_NON_SIGNER).approveProposal(proposalId); - }).toThrow('SignerManager: not a signer'); + it('should fail for non-signer', async () => { + await expect( + multisig.as('OTHER').approveProposal(proposalId), + ).rejects.toThrow('SignerManager: not a signer'); }); - it('should fail for double approval', () => { - multisig.as(SIGNER1).approveProposal(proposalId); - expect(() => { - multisig.as(SIGNER1).approveProposal(proposalId); - }).toThrow('Multisig: already approved'); + it('should fail for double approval', async () => { + await multisig.as('SIGNER1').approveProposal(proposalId); + await expect( + multisig.as('SIGNER1').approveProposal(proposalId), + ).rejects.toThrow('Multisig: already approved'); }); - it('should fail for non-existing proposal', () => { - expect(() => { - multisig.as(SIGNER1).approveProposal(999n); - }).toThrow('ProposalManager: proposal not found'); + it('should fail for non-existing proposal', async () => { + await expect( + multisig.as('SIGNER1').approveProposal(999n), + ).rejects.toThrow('ProposalManager: proposal not found'); }); - it('should fail for executed proposal', () => { - multisig.deposit(makeCoin(COLOR, AMOUNT)); - multisig.as(SIGNER1).approveProposal(proposalId); - multisig.as(SIGNER2).approveProposal(proposalId); - multisig.executeShieldedProposal(proposalId); + it('should fail for executed proposal', async () => { + await multisig.deposit(makeCoin(COLOR, AMOUNT)); + await multisig.as('SIGNER1').approveProposal(proposalId); + await multisig.as('SIGNER2').approveProposal(proposalId); + await multisig.executeShieldedProposal(proposalId); - expect(() => { - multisig.as(SIGNER3).approveProposal(proposalId); - }).toThrow('ProposalManager: proposal not active'); + await expect( + multisig.as('SIGNER3').approveProposal(proposalId), + ).rejects.toThrow('ProposalManager: proposal not active'); }); }); describe('revokeApproval', () => { let proposalId: bigint; - beforeEach(() => { + beforeEach(async () => { const to = makeRecipient(Z_RECIPIENT_PK); - proposalId = multisig - .as(SIGNER1) + proposalId = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); - multisig.as(SIGNER1).approveProposal(proposalId); + await multisig.as('SIGNER1').approveProposal(proposalId); }); - it('should allow signer to revoke their approval', () => { - multisig.as(SIGNER1).revokeApproval(proposalId); + it('should allow signer to revoke their approval', async () => { + await multisig.as('SIGNER1').revokeApproval(proposalId); expect( - multisig.isProposalApprovedBySigner(proposalId, Z_SIGNER1), + await multisig.isProposalApprovedBySigner(proposalId, Z_SIGNER1), ).toEqual(false); - expect(multisig.getApprovalCount(proposalId)).toEqual(0n); + expect(await multisig.getApprovalCount(proposalId)).toEqual(0n); }); - it('should fail for non-signer', () => { - expect(() => { - multisig.as(_NON_SIGNER).revokeApproval(proposalId); - }).toThrow('SignerManager: not a signer'); + it('should fail for non-signer', async () => { + await expect( + multisig.as('OTHER').revokeApproval(proposalId), + ).rejects.toThrow('SignerManager: not a signer'); }); - it('should fail if not yet approved', () => { - expect(() => { - multisig.as(SIGNER2).revokeApproval(proposalId); - }).toThrow('Multisig: not approved'); + it('should fail if not yet approved', async () => { + await expect( + multisig.as('SIGNER2').revokeApproval(proposalId), + ).rejects.toThrow('Multisig: not approved'); }); - it('should allow re-approval after revoke', () => { - multisig.as(SIGNER1).revokeApproval(proposalId); - multisig.as(SIGNER1).approveProposal(proposalId); + it('should allow re-approval after revoke', async () => { + await multisig.as('SIGNER1').revokeApproval(proposalId); + await multisig.as('SIGNER1').approveProposal(proposalId); expect( - multisig.isProposalApprovedBySigner(proposalId, Z_SIGNER1), + await multisig.isProposalApprovedBySigner(proposalId, Z_SIGNER1), ).toEqual(true); - expect(multisig.getApprovalCount(proposalId)).toEqual(1n); + expect(await multisig.getApprovalCount(proposalId)).toEqual(1n); }); - it('should fail for executed proposal', () => { - multisig.deposit(makeCoin(COLOR, AMOUNT)); - multisig.as(SIGNER2).approveProposal(proposalId); - multisig.executeShieldedProposal(proposalId); + it('should fail for executed proposal', async () => { + await multisig.deposit(makeCoin(COLOR, AMOUNT)); + await multisig.as('SIGNER2').approveProposal(proposalId); + await multisig.executeShieldedProposal(proposalId); - expect(() => { - multisig.as(SIGNER1).revokeApproval(proposalId); - }).toThrow('ProposalManager: proposal not active'); + await expect( + multisig.as('SIGNER1').revokeApproval(proposalId), + ).rejects.toThrow('ProposalManager: proposal not active'); }); }); describe('executeShieldedProposal', () => { let proposalId: bigint; - beforeEach(() => { + beforeEach(async () => { // Fund the treasury - multisig.deposit(makeCoin(COLOR, AMOUNT)); + await multisig.deposit(makeCoin(COLOR, AMOUNT)); // Create and approve proposal to threshold const to = makeRecipient(Z_RECIPIENT_PK); - proposalId = multisig - .as(SIGNER1) + proposalId = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); - multisig.as(SIGNER1).approveProposal(proposalId); - multisig.as(SIGNER2).approveProposal(proposalId); + await multisig.as('SIGNER1').approveProposal(proposalId); + await multisig.as('SIGNER2').approveProposal(proposalId); }); - it('should execute when threshold is met', () => { - multisig.executeShieldedProposal(proposalId); - expect(multisig.getProposalStatus(proposalId)).toEqual( + it('should execute when threshold is met', async () => { + await multisig.executeShieldedProposal(proposalId); + expect(await multisig.getProposalStatus(proposalId)).toEqual( ProposalStatus.Executed, ); }); - it('should return sent coin and change in result', () => { - const result = multisig.executeShieldedProposal(proposalId); + it('should return sent coin and change in result', async () => { + const result = await multisig.executeShieldedProposal(proposalId); expect(result.sent.value).toEqual(PROPOSAL_AMOUNT); expect(result.sent.color).toEqual(COLOR); expect(result.change.is_some).toEqual(true); @@ -299,229 +303,237 @@ describe('ShieldedMultiSig', () => { expect(result.change.value.color).toEqual(COLOR); }); - it('should return no change when sending full balance', () => { + it('should return no change when sending full balance', async () => { // Create proposal for the full amount const to = makeRecipient(Z_RECIPIENT_PK); - const fullId = multisig - .as(SIGNER1) + const fullId = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, AMOUNT); - multisig.as(SIGNER1).approveProposal(fullId); - multisig.as(SIGNER2).approveProposal(fullId); + await multisig.as('SIGNER1').approveProposal(fullId); + await multisig.as('SIGNER2').approveProposal(fullId); - const result = multisig.executeShieldedProposal(fullId); + const result = await multisig.executeShieldedProposal(fullId); expect(result.sent.value).toEqual(AMOUNT); expect(result.change.is_some).toEqual(false); }); - it('should deduct from treasury balance', () => { - multisig.executeShieldedProposal(proposalId); - expect(multisig.getTokenBalance(COLOR)).toEqual( + it('should deduct from treasury balance', async () => { + await multisig.executeShieldedProposal(proposalId); + expect(await multisig.getTokenBalance(COLOR)).toEqual( AMOUNT - PROPOSAL_AMOUNT, ); }); - it('should track sent total', () => { - multisig.executeShieldedProposal(proposalId); - expect(multisig.getSentTotal(COLOR)).toEqual(PROPOSAL_AMOUNT); + it('should track sent total', async () => { + await multisig.executeShieldedProposal(proposalId); + expect(await multisig.getSentTotal(COLOR)).toEqual(PROPOSAL_AMOUNT); }); - it('should fail when threshold is not met', () => { + it('should fail when threshold is not met', async () => { // Create a new proposal with only 1 approval const to = makeRecipient(Z_RECIPIENT_PK); - const id2 = multisig - .as(SIGNER1) + const id2 = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, 100n); - multisig.as(SIGNER1).approveProposal(id2); + await multisig.as('SIGNER1').approveProposal(id2); - expect(() => { - multisig.executeShieldedProposal(id2); - }).toThrow('SignerManager: threshold not met'); + await expect(multisig.executeShieldedProposal(id2)).rejects.toThrow( + 'SignerManager: threshold not met', + ); }); - it('should fail for non-existing proposal', () => { - expect(() => { - multisig.executeShieldedProposal(999n); - }).toThrow('ProposalManager: proposal not found'); + it('should fail for non-existing proposal', async () => { + await expect(multisig.executeShieldedProposal(999n)).rejects.toThrow( + 'ProposalManager: proposal not found', + ); }); - it('should fail when executed twice', () => { - multisig.executeShieldedProposal(proposalId); - expect(() => { - multisig.executeShieldedProposal(proposalId); - }).toThrow('ProposalManager: proposal not active'); + it('should fail when executed twice', async () => { + await multisig.executeShieldedProposal(proposalId); + await expect( + multisig.executeShieldedProposal(proposalId), + ).rejects.toThrow('ProposalManager: proposal not active'); }); - it('should fail with insufficient treasury balance', () => { + it('should fail with insufficient treasury balance', async () => { // Create proposal for more than treasury holds const to = makeRecipient(Z_RECIPIENT_PK); - const bigId = multisig - .as(SIGNER1) + const bigId = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, AMOUNT + 1n); - multisig.as(SIGNER1).approveProposal(bigId); - multisig.as(SIGNER2).approveProposal(bigId); + await multisig.as('SIGNER1').approveProposal(bigId); + await multisig.as('SIGNER2').approveProposal(bigId); - expect(() => { - multisig.executeShieldedProposal(bigId); - }).toThrow('ShieldedTreasury: coin value insufficient'); + await expect(multisig.executeShieldedProposal(bigId)).rejects.toThrow( + 'ShieldedTreasury: coin value insufficient', + ); }); }); describe('view - approvals', () => { - it('should return false for unapproved signer', () => { + it('should return false for unapproved signer', async () => { const to = makeRecipient(Z_RECIPIENT_PK); - const id = multisig - .as(SIGNER1) + const id = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); - expect(multisig.isProposalApprovedBySigner(id, Z_SIGNER1)).toEqual( - false, - ); + expect( + await multisig.isProposalApprovedBySigner(id, Z_SIGNER1), + ).toEqual(false); }); - it('should return 0 approval count for new proposal', () => { + it('should return 0 approval count for new proposal', async () => { const to = makeRecipient(Z_RECIPIENT_PK); - const id = multisig - .as(SIGNER1) + const id = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); - expect(multisig.getApprovalCount(id)).toEqual(0n); + expect(await multisig.getApprovalCount(id)).toEqual(0n); }); }); describe('view - proposal delegation', () => { let proposalId: bigint; - beforeEach(() => { + beforeEach(async () => { const to = makeRecipient(Z_RECIPIENT_PK); - proposalId = multisig - .as(SIGNER1) + proposalId = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); }); - it('getProposalRecipient should return recipient', () => { - const recipient = multisig.getProposalRecipient(proposalId); + it('getProposalRecipient should return recipient', async () => { + const recipient = await multisig.getProposalRecipient(proposalId); expect(recipient.kind).toEqual(RecipientKind.ShieldedUser); expect(recipient.address).toEqual(Z_RECIPIENT_PK.bytes); }); - it('getProposalAmount should return amount', () => { - expect(multisig.getProposalAmount(proposalId)).toEqual(PROPOSAL_AMOUNT); + it('getProposalAmount should return amount', async () => { + expect(await multisig.getProposalAmount(proposalId)).toEqual( + PROPOSAL_AMOUNT, + ); }); - it('getProposalColor should return color', () => { - expect(multisig.getProposalColor(proposalId)).toEqual(COLOR); + it('getProposalColor should return color', async () => { + expect(await multisig.getProposalColor(proposalId)).toEqual(COLOR); }); }); describe('view - signer manager delegation', () => { - it('getSignerCount should match initial count', () => { - expect(multisig.getSignerCount()).toEqual(BigInt(SIGNERS.length)); + it('getSignerCount should match initial count', async () => { + expect(await multisig.getSignerCount()).toEqual(BigInt(SIGNERS.length)); }); - it('getThreshold should match initial threshold', () => { - expect(multisig.getThreshold()).toEqual(THRESHOLD); + it('getThreshold should match initial threshold', async () => { + expect(await multisig.getThreshold()).toEqual(THRESHOLD); }); - it('isSigner should return true for signer', () => { - expect(multisig.isSigner(Z_SIGNER1)).toEqual(true); + it('isSigner should return true for signer', async () => { + expect(await multisig.isSigner(Z_SIGNER1)).toEqual(true); }); - it('isSigner should return false for non-signer', () => { - expect(multisig.isSigner(Z_NON_SIGNER)).toEqual(false); + it('isSigner should return false for non-signer', async () => { + expect(await multisig.isSigner(Z_NON_SIGNER)).toEqual(false); }); }); describe('view - treasury delegation', () => { - beforeEach(() => { - multisig.deposit(makeCoin(COLOR, AMOUNT)); + beforeEach(async () => { + await multisig.deposit(makeCoin(COLOR, AMOUNT)); }); - it('getTokenBalance should reflect deposits', () => { - expect(multisig.getTokenBalance(COLOR)).toEqual(AMOUNT); + it('getTokenBalance should reflect deposits', async () => { + expect(await multisig.getTokenBalance(COLOR)).toEqual(AMOUNT); }); - it('getReceivedTotal should reflect deposits', () => { - expect(multisig.getReceivedTotal(COLOR)).toEqual(AMOUNT); + it('getReceivedTotal should reflect deposits', async () => { + expect(await multisig.getReceivedTotal(COLOR)).toEqual(AMOUNT); }); - it('getSentTotal should be 0 before any sends', () => { - expect(multisig.getSentTotal(COLOR)).toEqual(0n); + it('getSentTotal should be 0 before any sends', async () => { + expect(await multisig.getSentTotal(COLOR)).toEqual(0n); }); - it('getReceivedMinusSent should equal balance', () => { - expect(multisig.getReceivedMinusSent(COLOR)).toEqual(AMOUNT); + it('getReceivedMinusSent should equal balance', async () => { + expect(await multisig.getReceivedMinusSent(COLOR)).toEqual(AMOUNT); }); }); describe('full lifecycle', () => { - it('should handle deposit -> propose -> approve -> execute', () => { + it('should handle deposit -> propose -> approve -> execute', async () => { // Deposit - multisig.deposit(makeCoin(COLOR, AMOUNT)); - expect(multisig.getTokenBalance(COLOR)).toEqual(AMOUNT); + await multisig.deposit(makeCoin(COLOR, AMOUNT)); + expect(await multisig.getTokenBalance(COLOR)).toEqual(AMOUNT); // Propose const to = makeRecipient(Z_RECIPIENT_PK); - const id = multisig - .as(SIGNER1) + const id = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); // Approve to threshold - multisig.as(SIGNER1).approveProposal(id); - multisig.as(SIGNER2).approveProposal(id); - expect(multisig.getApprovalCount(id)).toEqual(THRESHOLD); + await multisig.as('SIGNER1').approveProposal(id); + await multisig.as('SIGNER2').approveProposal(id); + expect(await multisig.getApprovalCount(id)).toEqual(THRESHOLD); // Execute - multisig.executeShieldedProposal(id); - expect(multisig.getProposalStatus(id)).toEqual(ProposalStatus.Executed); - expect(multisig.getTokenBalance(COLOR)).toEqual( + await multisig.executeShieldedProposal(id); + expect(await multisig.getProposalStatus(id)).toEqual( + ProposalStatus.Executed, + ); + expect(await multisig.getTokenBalance(COLOR)).toEqual( AMOUNT - PROPOSAL_AMOUNT, ); - expect(multisig.getReceivedMinusSent(COLOR)).toEqual( + expect(await multisig.getReceivedMinusSent(COLOR)).toEqual( AMOUNT - PROPOSAL_AMOUNT, ); }); - it('should handle multiple proposals concurrently', () => { - multisig.deposit(makeCoin(COLOR, AMOUNT)); + it('should handle multiple proposals concurrently', async () => { + await multisig.deposit(makeCoin(COLOR, AMOUNT)); const to = makeRecipient(Z_RECIPIENT_PK); - const id1 = multisig - .as(SIGNER1) + const id1 = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, 200n); - const id2 = multisig - .as(SIGNER2) + const id2 = await multisig + .as('SIGNER2') .createShieldedProposal(to, COLOR, 300n); // Approve and execute first - multisig.as(SIGNER1).approveProposal(id1); - multisig.as(SIGNER2).approveProposal(id1); - multisig.executeShieldedProposal(id1); + await multisig.as('SIGNER1').approveProposal(id1); + await multisig.as('SIGNER2').approveProposal(id1); + await multisig.executeShieldedProposal(id1); // Approve and execute second - multisig.as(SIGNER1).approveProposal(id2); - multisig.as(SIGNER3).approveProposal(id2); - multisig.executeShieldedProposal(id2); + await multisig.as('SIGNER1').approveProposal(id2); + await multisig.as('SIGNER3').approveProposal(id2); + await multisig.executeShieldedProposal(id2); - expect(multisig.getTokenBalance(COLOR)).toEqual(AMOUNT - 200n - 300n); + expect(await multisig.getTokenBalance(COLOR)).toEqual( + AMOUNT - 200n - 300n, + ); }); - it('should handle approve -> revoke -> re-approve -> execute', () => { - multisig.deposit(makeCoin(COLOR, AMOUNT)); + it('should handle approve -> revoke -> re-approve -> execute', async () => { + await multisig.deposit(makeCoin(COLOR, AMOUNT)); const to = makeRecipient(Z_RECIPIENT_PK); - const id = multisig - .as(SIGNER1) + const id = await multisig + .as('SIGNER1') .createShieldedProposal(to, COLOR, PROPOSAL_AMOUNT); // Approve then revoke - multisig.as(SIGNER1).approveProposal(id); - multisig.as(SIGNER1).revokeApproval(id); - expect(multisig.getApprovalCount(id)).toEqual(0n); + await multisig.as('SIGNER1').approveProposal(id); + await multisig.as('SIGNER1').revokeApproval(id); + expect(await multisig.getApprovalCount(id)).toEqual(0n); // Re-approve with enough signers - multisig.as(SIGNER2).approveProposal(id); - multisig.as(SIGNER3).approveProposal(id); - expect(multisig.getApprovalCount(id)).toEqual(2n); + await multisig.as('SIGNER2').approveProposal(id); + await multisig.as('SIGNER3').approveProposal(id); + expect(await multisig.getApprovalCount(id)).toEqual(2n); - multisig.executeShieldedProposal(id); - expect(multisig.getProposalStatus(id)).toEqual(ProposalStatus.Executed); + await multisig.executeShieldedProposal(id); + expect(await multisig.getProposalStatus(id)).toEqual( + ProposalStatus.Executed, + ); }); }); }); diff --git a/contracts/src/multisig/test/ShieldedMultiSigV2.test.ts b/contracts/src/multisig/test/ShieldedMultiSigV2.test.ts index ebe07316..3dea44c7 100644 --- a/contracts/src/multisig/test/ShieldedMultiSigV2.test.ts +++ b/contracts/src/multisig/test/ShieldedMultiSigV2.test.ts @@ -70,52 +70,60 @@ let multisig: ShieldedMultiSigV2Simulator; describe('ShieldedMultiSigV2', () => { describe('constructor', () => { - it('should initialize with 2-of-3 threshold', () => { - multisig = new ShieldedMultiSigV2Simulator( + it('should initialize with 2-of-3 threshold', async () => { + multisig = await ShieldedMultiSigV2Simulator.create( INSTANCE_SALT, SIGNER_COMMITMENTS, 2n, ); - expect(multisig.getSignerCount()).toEqual(3n); - expect(multisig.getThreshold()).toEqual(2n); + expect(await multisig.getSignerCount()).toEqual(3n); + expect(await multisig.getThreshold()).toEqual(2n); }); - it('should initialize with 1-of-3 threshold', () => { - multisig = new ShieldedMultiSigV2Simulator( + it('should initialize with 1-of-3 threshold', async () => { + multisig = await ShieldedMultiSigV2Simulator.create( INSTANCE_SALT, SIGNER_COMMITMENTS, 1n, ); - expect(multisig.getThreshold()).toEqual(1n); + expect(await multisig.getThreshold()).toEqual(1n); }); - it('should fail with zero threshold', () => { - expect(() => { - new ShieldedMultiSigV2Simulator(INSTANCE_SALT, SIGNER_COMMITMENTS, 0n); - }).toThrow('SignerManager: threshold must be > 0'); + it('should fail with zero threshold', async () => { + await expect( + ShieldedMultiSigV2Simulator.create( + INSTANCE_SALT, + SIGNER_COMMITMENTS, + 0n, + ), + ).rejects.toThrow('SignerManager: threshold must be > 0'); }); - it('should fail with threshold greater than 2', () => { - expect(() => { - new ShieldedMultiSigV2Simulator(INSTANCE_SALT, SIGNER_COMMITMENTS, 3n); - }).toThrow( + it('should fail with threshold greater than 2', async () => { + await expect( + ShieldedMultiSigV2Simulator.create( + INSTANCE_SALT, + SIGNER_COMMITMENTS, + 3n, + ), + ).rejects.toThrow( 'ShieldedMultiSigV2: threshold cannot exceed 2 (execute verifies at most 2 signatures)', ); }); - it('should register all signer commitments', () => { - multisig = new ShieldedMultiSigV2Simulator( + it('should register all signer commitments', async () => { + multisig = await ShieldedMultiSigV2Simulator.create( INSTANCE_SALT, SIGNER_COMMITMENTS, 2n, ); for (const commitment of SIGNER_COMMITMENTS) { - expect(multisig.isSigner(commitment)).toEqual(true); + expect(await multisig.isSigner(commitment)).toEqual(true); } }); - it('should reject a non-signer commitment', () => { - multisig = new ShieldedMultiSigV2Simulator( + it('should reject a non-signer commitment', async () => { + multisig = await ShieldedMultiSigV2Simulator.create( INSTANCE_SALT, SIGNER_COMMITMENTS, 2n, @@ -124,13 +132,13 @@ describe('ShieldedMultiSigV2', () => { NON_SIGNER_PK, INSTANCE_SALT, ); - expect(multisig.isSigner(unknown)).toEqual(false); + expect(await multisig.isSigner(unknown)).toEqual(false); }); }); describe('when initialized', () => { - beforeEach(() => { - multisig = new ShieldedMultiSigV2Simulator( + beforeEach(async () => { + multisig = await ShieldedMultiSigV2Simulator.create( INSTANCE_SALT, SIGNER_COMMITMENTS, 2n, @@ -138,48 +146,46 @@ describe('ShieldedMultiSigV2', () => { }); describe('view', () => { - it('getNonce should start at 0', () => { - expect(multisig.getNonce()).toEqual(0n); + it('getNonce should start at 0', async () => { + expect(await multisig.getNonce()).toEqual(0n); }); - it('getSignerCount should return 3', () => { - expect(multisig.getSignerCount()).toEqual(3n); + it('getSignerCount should return 3', async () => { + expect(await multisig.getSignerCount()).toEqual(3n); }); - it('getThreshold should match constructor arg', () => { - expect(multisig.getThreshold()).toEqual(2n); + it('getThreshold should match constructor arg', async () => { + expect(await multisig.getThreshold()).toEqual(2n); }); }); describe('deposit', () => { - it('should accept deposits without reverting', () => { - expect(() => { - multisig.deposit(makeCoin(COLOR, AMOUNT)); - }).not.toThrow(); + it('should accept deposits without reverting', async () => { + await multisig.deposit(makeCoin(COLOR, AMOUNT)); }); }); describe('execute', () => { - it('should reject duplicate signer', () => { + it('should reject duplicate signer', async () => { const to = makeRecipient(new Uint8Array(32).fill(7)); const coin = makeQualifiedCoin(COLOR, AMOUNT, 0n); - expect(() => { - multisig.execute(to, 100n, coin, [PK1, PK1], [DUMMY_SIG, DUMMY_SIG]); - }).toThrow('Multisig: duplicate signer'); + await expect( + multisig.execute(to, 100n, coin, [PK1, PK1], [DUMMY_SIG, DUMMY_SIG]), + ).rejects.toThrow('Multisig: duplicate signer'); }); - it('should reject a non-signer pubkey', () => { + it('should reject a non-signer pubkey', async () => { const to = makeRecipient(new Uint8Array(32).fill(7)); const coin = makeQualifiedCoin(COLOR, AMOUNT, 0n); - expect(() => { + await expect( multisig.execute( to, 100n, coin, [PK1, NON_SIGNER_PK], [DUMMY_SIG, DUMMY_SIG], - ); - }).toThrow('SignerManager: not a signer'); + ), + ).rejects.toThrow('SignerManager: not a signer'); }); }); }); diff --git a/contracts/src/multisig/test/ShieldedMultiSigV3.test.ts b/contracts/src/multisig/test/ShieldedMultiSigV3.test.ts index 240c9baf..dcf3900b 100644 --- a/contracts/src/multisig/test/ShieldedMultiSigV3.test.ts +++ b/contracts/src/multisig/test/ShieldedMultiSigV3.test.ts @@ -50,65 +50,68 @@ let multisig: ShieldedMultiSigV3Simulator; describe('ShieldedMultiSigV3', () => { describe('constructor', () => { - it('should initialize', () => { - multisig = new ShieldedMultiSigV3Simulator( + it('should initialize', async () => { + multisig = await ShieldedMultiSigV3Simulator.create( INSTANCE_SALT, INIT_COIN_NONCE, TOKEN_DOMAIN, SIGNER_COMMITMENTS, ); - expect(multisig.getSignerCount()).toEqual(3n); - expect(multisig.getThreshold()).toEqual(2n); + expect(await multisig.getSignerCount()).toEqual(3n); + expect(await multisig.getThreshold()).toEqual(2n); }); - it('should register all signer commitments', () => { - multisig = new ShieldedMultiSigV3Simulator( + it('should register all signer commitments', async () => { + multisig = await ShieldedMultiSigV3Simulator.create( INSTANCE_SALT, INIT_COIN_NONCE, TOKEN_DOMAIN, SIGNER_COMMITMENTS, ); for (const commitment of SIGNER_COMMITMENTS) { - expect(multisig.isSigner(commitment)).toEqual(true); + expect(await multisig.isSigner(commitment)).toEqual(true); } }); - it('should reject a non-signer commitment', () => { - multisig = new ShieldedMultiSigV3Simulator( + it('should reject a non-signer commitment', async () => { + multisig = await ShieldedMultiSigV3Simulator.create( INSTANCE_SALT, INIT_COIN_NONCE, TOKEN_DOMAIN, SIGNER_COMMITMENTS, ); - const unknown = multisig._calculateSignerId(NON_SIGNER_PK, INSTANCE_SALT); - expect(multisig.isSigner(unknown)).toEqual(false); + const unknown = await multisig._calculateSignerId( + NON_SIGNER_PK, + INSTANCE_SALT, + ); + expect(await multisig.isSigner(unknown)).toEqual(false); }); - it('should fail with duplicate signer commitments', () => { - expect(() => { - new ShieldedMultiSigV3Simulator( + it('should fail with duplicate signer commitments', async () => { + await expect( + ShieldedMultiSigV3Simulator.create( INSTANCE_SALT, INIT_COIN_NONCE, TOKEN_DOMAIN, [COMMITMENT1, COMMITMENT1, COMMITMENT2], - ); - }).toThrow('Signer: signer already active'); + ), + ).rejects.toThrow('Signer: signer already active'); }); - it('should store token domain', () => { - multisig = new ShieldedMultiSigV3Simulator( + it('should store token domain', async () => { + multisig = await ShieldedMultiSigV3Simulator.create( INSTANCE_SALT, INIT_COIN_NONCE, TOKEN_DOMAIN, SIGNER_COMMITMENTS, ); - expect(multisig.getTokenDomain()).toEqual(TOKEN_DOMAIN); + expect(await multisig.getTokenDomain()).toEqual(TOKEN_DOMAIN); }); }); describe('when initialized', () => { - beforeEach(() => { - multisig = new ShieldedMultiSigV3Simulator( + beforeEach(async () => { + multisig = await ShieldedMultiSigV3Simulator.create( INSTANCE_SALT, INIT_COIN_NONCE, TOKEN_DOMAIN, @@ -117,293 +120,310 @@ describe('ShieldedMultiSigV3', () => { }); describe('view', () => { - it('getNonce should start at 0', () => { - expect(multisig.getNonce()).toEqual(0n); + it('getNonce should start at 0', async () => { + expect(await multisig.getNonce()).toEqual(0n); }); - it('getSignerCount should return 3', () => { - expect(multisig.getSignerCount()).toEqual(3n); + it('getSignerCount should return 3', async () => { + expect(await multisig.getSignerCount()).toEqual(3n); }); - it('getThreshold should match constructor arg', () => { - expect(multisig.getThreshold()).toEqual(2n); + it('getThreshold should match constructor arg', async () => { + expect(await multisig.getThreshold()).toEqual(2n); }); - it('getTokenType should return non-zero', () => { - expect(multisig.getTokenType()).not.toEqual(new Uint8Array(32)); + it('getTokenType should return non-zero', async () => { + expect(await multisig.getTokenType()).not.toEqual(new Uint8Array(32)); }); - it('getTokenType should be deterministic', () => { - expect(multisig.getTokenType()).toEqual(multisig.getTokenType()); + it('getTokenType should be deterministic', async () => { + expect(await multisig.getTokenType()).toEqual( + await multisig.getTokenType(), + ); }); }); describe('_calculateSignerId', () => { - it('should produce deterministic commitments', () => { - const c1 = multisig._calculateSignerId(PK1, INSTANCE_SALT); - const c2 = multisig._calculateSignerId(PK1, INSTANCE_SALT); + it('should produce deterministic commitments', async () => { + const c1 = await multisig._calculateSignerId(PK1, INSTANCE_SALT); + const c2 = await multisig._calculateSignerId(PK1, INSTANCE_SALT); expect(c1).toEqual(c2); }); - it('should produce different commitments for different keys', () => { - const c1 = multisig._calculateSignerId(PK1, INSTANCE_SALT); - const c2 = multisig._calculateSignerId(PK2, INSTANCE_SALT); + it('should produce different commitments for different keys', async () => { + const c1 = await multisig._calculateSignerId(PK1, INSTANCE_SALT); + const c2 = await multisig._calculateSignerId(PK2, INSTANCE_SALT); expect(c1).not.toEqual(c2); }); - it('should produce different commitments for different salts', () => { + it('should produce different commitments for different salts', async () => { const salt2 = new Uint8Array(32).fill(0xcc); - const c1 = multisig._calculateSignerId(PK1, INSTANCE_SALT); - const c2 = multisig._calculateSignerId(PK1, salt2); + const c1 = await multisig._calculateSignerId(PK1, INSTANCE_SALT); + const c2 = await multisig._calculateSignerId(PK1, salt2); expect(c1).not.toEqual(c2); }); - it('should match registered commitments', () => { - expect(multisig._calculateSignerId(PK1, INSTANCE_SALT)).toEqual( + it('should match registered commitments', async () => { + expect(await multisig._calculateSignerId(PK1, INSTANCE_SALT)).toEqual( COMMITMENT1, ); - expect(multisig._calculateSignerId(PK2, INSTANCE_SALT)).toEqual( + expect(await multisig._calculateSignerId(PK2, INSTANCE_SALT)).toEqual( COMMITMENT2, ); - expect(multisig._calculateSignerId(PK3, INSTANCE_SALT)).toEqual( + expect(await multisig._calculateSignerId(PK3, INSTANCE_SALT)).toEqual( COMMITMENT3, ); }); }); describe('mint', () => { - it('should mint to a user recipient with signers 0 and 1', () => { - expect(() => { - multisig.mint( - 100n, - USER_RECIPIENT, - [PK1, PK2], - [DUMMY_SIG, DUMMY_SIG], - ); - }).not.toThrow(); + it('should mint to a user recipient with signers 0 and 1', async () => { + await multisig.mint( + 100n, + USER_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); }); - it('should mint to a user recipient with signers 0 and 2', () => { - expect(() => { - multisig.mint( - 100n, - USER_RECIPIENT, - [PK1, PK3], - [DUMMY_SIG, DUMMY_SIG], - ); - }).not.toThrow(); + it('should mint to a user recipient with signers 0 and 2', async () => { + await multisig.mint( + 100n, + USER_RECIPIENT, + [PK1, PK3], + [DUMMY_SIG, DUMMY_SIG], + ); }); - it('should mint to a user recipient with signers 1 and 2', () => { - expect(() => { - multisig.mint( - 100n, - USER_RECIPIENT, - [PK2, PK3], - [DUMMY_SIG, DUMMY_SIG], - ); - }).not.toThrow(); + it('should mint to a user recipient with signers 1 and 2', async () => { + await multisig.mint( + 100n, + USER_RECIPIENT, + [PK2, PK3], + [DUMMY_SIG, DUMMY_SIG], + ); }); - it('should mint to a contract recipient', () => { - expect(() => { - multisig.mint( - 100n, - CONTRACT_RECIPIENT, - [PK1, PK2], - [DUMMY_SIG, DUMMY_SIG], - ); - }).not.toThrow(); + it('should mint to a contract recipient', async () => { + await multisig.mint( + 100n, + CONTRACT_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); }); - it('should reject duplicate signer', () => { - expect(() => { + it('should reject duplicate signer', async () => { + await expect( multisig.mint( 100n, USER_RECIPIENT, [PK1, PK1], [DUMMY_SIG, DUMMY_SIG], - ); - }).toThrow('Multisig: duplicate signer'); + ), + ).rejects.toThrow('Multisig: duplicate signer'); }); - it('should reject a non-signer pubkey', () => { - expect(() => { + it('should reject a non-signer pubkey', async () => { + await expect( multisig.mint( 100n, USER_RECIPIENT, [PK1, NON_SIGNER_PK], [DUMMY_SIG, DUMMY_SIG], - ); - }).toThrow('Signer: not a signer'); + ), + ).rejects.toThrow('Signer: not a signer'); }); - it('should increment nonce after mint', () => { - expect(multisig.getNonce()).toEqual(0n); - multisig.mint(100n, USER_RECIPIENT, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - expect(multisig.getNonce()).toEqual(1n); + it('should increment nonce after mint', async () => { + expect(await multisig.getNonce()).toEqual(0n); + await multisig.mint( + 100n, + USER_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); + expect(await multisig.getNonce()).toEqual(1n); }); - it('should increment nonce on each mint', () => { - multisig.mint(100n, USER_RECIPIENT, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - multisig.mint(200n, USER_RECIPIENT, [PK1, PK3], [DUMMY_SIG, DUMMY_SIG]); - multisig.mint( + it('should increment nonce on each mint', async () => { + await multisig.mint( + 100n, + USER_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); + await multisig.mint( + 200n, + USER_RECIPIENT, + [PK1, PK3], + [DUMMY_SIG, DUMMY_SIG], + ); + await multisig.mint( 300n, CONTRACT_RECIPIENT, [PK2, PK3], [DUMMY_SIG, DUMMY_SIG], ); - expect(multisig.getNonce()).toEqual(3n); + expect(await multisig.getNonce()).toEqual(3n); }); - it('should accept zero amount', () => { - expect(() => { - multisig.mint(0n, USER_RECIPIENT, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - }).not.toThrow(); + it('should accept zero amount', async () => { + await multisig.mint( + 0n, + USER_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); }); - it('should prevent replay by incrementing nonce', () => { - multisig.mint(100n, USER_RECIPIENT, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); + it('should prevent replay by incrementing nonce', async () => { + await multisig.mint( + 100n, + USER_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); // Second mint with same params succeeds because nonce is different // (stub ver doesn't actually check signatures) - expect(() => { - multisig.mint( - 100n, - USER_RECIPIENT, - [PK1, PK2], - [DUMMY_SIG, DUMMY_SIG], - ); - }).not.toThrow(); - expect(multisig.getNonce()).toEqual(2n); + await multisig.mint( + 100n, + USER_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); + expect(await multisig.getNonce()).toEqual(2n); }); }); describe('burn', () => { - it('should burn with valid coin and signers 0 and 1', () => { - const coin = makeQualifiedCoin(multisig.getTokenType(), 100n); - expect(() => { - multisig.burn(coin, 100n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - }).not.toThrow(); + it('should burn with valid coin and signers 0 and 1', async () => { + const coin = makeQualifiedCoin(await multisig.getTokenType(), 100n); + await multisig.burn(coin, 100n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); }); - it('should burn with signers 0 and 2', () => { - const coin = makeQualifiedCoin(multisig.getTokenType(), 100n); - expect(() => { - multisig.burn(coin, 100n, [PK1, PK3], [DUMMY_SIG, DUMMY_SIG]); - }).not.toThrow(); + it('should burn with signers 0 and 2', async () => { + const coin = makeQualifiedCoin(await multisig.getTokenType(), 100n); + await multisig.burn(coin, 100n, [PK1, PK3], [DUMMY_SIG, DUMMY_SIG]); }); - it('should burn with signers 1 and 2', () => { - const coin = makeQualifiedCoin(multisig.getTokenType(), 100n); - expect(() => { - multisig.burn(coin, 100n, [PK2, PK3], [DUMMY_SIG, DUMMY_SIG]); - }).not.toThrow(); + it('should burn with signers 1 and 2', async () => { + const coin = makeQualifiedCoin(await multisig.getTokenType(), 100n); + await multisig.burn(coin, 100n, [PK2, PK3], [DUMMY_SIG, DUMMY_SIG]); }); - it('should burn partial amount', () => { - const coin = makeQualifiedCoin(multisig.getTokenType(), 100n); - expect(() => { - multisig.burn(coin, 50n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - }).not.toThrow(); + it('should burn partial amount', async () => { + const coin = makeQualifiedCoin(await multisig.getTokenType(), 100n); + await multisig.burn(coin, 50n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); }); - it('should handle zero burn amount', () => { - const coin = makeQualifiedCoin(multisig.getTokenType(), 100n); - expect(() => { - multisig.burn(coin, 0n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - }).not.toThrow(); + it('should handle zero burn amount', async () => { + const coin = makeQualifiedCoin(await multisig.getTokenType(), 100n); + await multisig.burn(coin, 0n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); }); - it('should reject duplicate signer', () => { - const coin = makeQualifiedCoin(multisig.getTokenType(), 100n); - expect(() => { - multisig.burn(coin, 100n, [PK1, PK1], [DUMMY_SIG, DUMMY_SIG]); - }).toThrow('Multisig: duplicate signer'); + it('should reject duplicate signer', async () => { + const coin = makeQualifiedCoin(await multisig.getTokenType(), 100n); + await expect( + multisig.burn(coin, 100n, [PK1, PK1], [DUMMY_SIG, DUMMY_SIG]), + ).rejects.toThrow('Multisig: duplicate signer'); }); - it('should reject a non-signer pubkey', () => { - const coin = makeQualifiedCoin(multisig.getTokenType(), 100n); - expect(() => { + it('should reject a non-signer pubkey', async () => { + const coin = makeQualifiedCoin(await multisig.getTokenType(), 100n); + await expect( multisig.burn( coin, 100n, [PK1, NON_SIGNER_PK], [DUMMY_SIG, DUMMY_SIG], - ); - }).toThrow('Signer: not a signer'); + ), + ).rejects.toThrow('Signer: not a signer'); }); - it('should reject wrong token color', () => { + it('should reject wrong token color', async () => { const wrongColor = new Uint8Array(32).fill(0xde); const coin = makeQualifiedCoin(wrongColor, 100n); - expect(() => { - multisig.burn(coin, 100n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - }).toThrow('Multisig: coin not from this contract'); + await expect( + multisig.burn(coin, 100n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]), + ).rejects.toThrow('Multisig: coin not from this contract'); }); - it('should reject insufficient coin value', () => { - const coin = makeQualifiedCoin(multisig.getTokenType(), 10n); - expect(() => { - multisig.burn(coin, 100n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - }).toThrow('Multisig: insufficient coin value'); + it('should reject insufficient coin value', async () => { + const coin = makeQualifiedCoin(await multisig.getTokenType(), 10n); + await expect( + multisig.burn(coin, 100n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]), + ).rejects.toThrow('Multisig: insufficient coin value'); }); - it('should reject when amount exceeds value by 1', () => { - const coin = makeQualifiedCoin(multisig.getTokenType(), 99n); - expect(() => { - multisig.burn(coin, 100n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - }).toThrow('Multisig: insufficient coin value'); + it('should reject when amount exceeds value by 1', async () => { + const coin = makeQualifiedCoin(await multisig.getTokenType(), 99n); + await expect( + multisig.burn(coin, 100n, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]), + ).rejects.toThrow('Multisig: insufficient coin value'); }); - it('should share nonce across mint and burn', () => { - multisig.mint(100n, USER_RECIPIENT, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - expect(multisig.getNonce()).toEqual(1n); + it('should share nonce across mint and burn', async () => { + await multisig.mint( + 100n, + USER_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); + expect(await multisig.getNonce()).toEqual(1n); - const coin = makeQualifiedCoin(multisig.getTokenType(), 100n); - multisig.burn(coin, 50n, [PK1, PK3], [DUMMY_SIG, DUMMY_SIG]); - expect(multisig.getNonce()).toEqual(2n); + const coin = makeQualifiedCoin(await multisig.getTokenType(), 100n); + await multisig.burn(coin, 50n, [PK1, PK3], [DUMMY_SIG, DUMMY_SIG]); + expect(await multisig.getNonce()).toEqual(2n); }); }); describe('domain separation', () => { - it('should isolate signers across instances with different salts', () => { + it('should isolate signers across instances with different salts', async () => { const salt2 = new Uint8Array(32).fill(0xcc); - const c1 = multisig._calculateSignerId(PK1, INSTANCE_SALT); - const c2 = multisig._calculateSignerId(PK1, salt2); + const c1 = await multisig._calculateSignerId(PK1, INSTANCE_SALT); + const c2 = await multisig._calculateSignerId(PK1, salt2); expect(c1).not.toEqual(c2); }); - it('should derive different token types with different domains', () => { + it('should derive different token types with different domains', async () => { const altDomain = new Uint8Array(32); Buffer.from('alt:token:').copy(altDomain); - const alt = new ShieldedMultiSigV3Simulator( + const alt = await ShieldedMultiSigV3Simulator.create( INSTANCE_SALT, INIT_COIN_NONCE, altDomain, SIGNER_COMMITMENTS, ); - expect(multisig.getTokenType()).not.toEqual(alt.getTokenType()); + expect(await multisig.getTokenType()).not.toEqual( + await alt.getTokenType(), + ); }); }); describe('nonce', () => { - it('should start at 0', () => { - expect(multisig.getNonce()).toEqual(0n); + it('should start at 0', async () => { + expect(await multisig.getNonce()).toEqual(0n); }); - it('should increment monotonically', () => { + it('should increment monotonically', async () => { for (let i = 0; i < 5; i++) { - multisig.mint(1n, USER_RECIPIENT, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - expect(multisig.getNonce()).toEqual(BigInt(i + 1)); + await multisig.mint( + 1n, + USER_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); + expect(await multisig.getNonce()).toEqual(BigInt(i + 1)); } }); }); describe('cross-instance replay', () => { - it('should derive different message hashes for different instances', () => { - const instance2 = new ShieldedMultiSigV3Simulator( + it('should derive different message hashes for different instances', async () => { + const instance2 = await ShieldedMultiSigV3Simulator.create( INSTANCE_SALT, INIT_COIN_NONCE, TOKEN_DOMAIN, @@ -413,16 +433,21 @@ describe('ShieldedMultiSigV3', () => { // With stub verification, both succeed independently. // Once real ECDSA is available, a signature produced for one // instance's message hash must not validate against the other's. - multisig.mint(100n, USER_RECIPIENT, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG]); - instance2.mint( + await multisig.mint( + 100n, + USER_RECIPIENT, + [PK1, PK2], + [DUMMY_SIG, DUMMY_SIG], + ); + await instance2.mint( 100n, USER_RECIPIENT, [PK1, PK2], [DUMMY_SIG, DUMMY_SIG], ); - expect(multisig.getNonce()).toEqual(1n); - expect(instance2.getNonce()).toEqual(1n); + expect(await multisig.getNonce()).toEqual(1n); + expect(await instance2.getNonce()).toEqual(1n); }); }); }); diff --git a/contracts/src/multisig/test/ShieldedTreasury.test.ts b/contracts/src/multisig/test/ShieldedTreasury.test.ts index 01fe3281..6295e7e2 100644 --- a/contracts/src/multisig/test/ShieldedTreasury.test.ts +++ b/contracts/src/multisig/test/ShieldedTreasury.test.ts @@ -23,120 +23,126 @@ function makeCoin( let treasury: ShieldedTreasurySimulator; describe('ShieldedTreasury', () => { - beforeEach(() => { - treasury = new ShieldedTreasurySimulator(); + beforeEach(async () => { + treasury = await ShieldedTreasurySimulator.create(); }); describe('initial state', () => { - it('should return 0 balance for unknown color', () => { - expect(treasury.getTokenBalance(COLOR)).toEqual(0n); + it('should return 0 balance for unknown color', async () => { + expect(await treasury.getTokenBalance(COLOR)).toEqual(0n); }); - it('should return 0 received total for unknown color', () => { - expect(treasury.getReceivedTotal(COLOR)).toEqual(0n); + it('should return 0 received total for unknown color', async () => { + expect(await treasury.getReceivedTotal(COLOR)).toEqual(0n); }); - it('should return 0 sent total for unknown color', () => { - expect(treasury.getSentTotal(COLOR)).toEqual(0n); + it('should return 0 sent total for unknown color', async () => { + expect(await treasury.getSentTotal(COLOR)).toEqual(0n); }); - it('should return 0 receivedMinusSent for unknown color', () => { - expect(treasury.getReceivedMinusSent(COLOR)).toEqual(0n); + it('should return 0 receivedMinusSent for unknown color', async () => { + expect(await treasury.getReceivedMinusSent(COLOR)).toEqual(0n); }); }); describe('_deposit', () => { - it('should deposit and update balance', () => { - treasury._deposit(makeCoin(COLOR, AMOUNT)); - expect(treasury.getTokenBalance(COLOR)).toEqual(AMOUNT); + it('should deposit and update balance', async () => { + await treasury._deposit(makeCoin(COLOR, AMOUNT)); + expect(await treasury.getTokenBalance(COLOR)).toEqual(AMOUNT); }); - it('should track received total', () => { - treasury._deposit(makeCoin(COLOR, AMOUNT)); - expect(treasury.getReceivedTotal(COLOR)).toEqual(AMOUNT); + it('should track received total', async () => { + await treasury._deposit(makeCoin(COLOR, AMOUNT)); + expect(await treasury.getReceivedTotal(COLOR)).toEqual(AMOUNT); }); - it('should accumulate multiple deposits', () => { - treasury._deposit(makeCoin(COLOR, AMOUNT, new Uint8Array(32).fill(1))); - treasury._deposit(makeCoin(COLOR, AMOUNT, new Uint8Array(32).fill(2))); - expect(treasury.getTokenBalance(COLOR)).toEqual(AMOUNT * 2n); - expect(treasury.getReceivedTotal(COLOR)).toEqual(AMOUNT * 2n); + it('should accumulate multiple deposits', async () => { + await treasury._deposit( + makeCoin(COLOR, AMOUNT, new Uint8Array(32).fill(1)), + ); + await treasury._deposit( + makeCoin(COLOR, AMOUNT, new Uint8Array(32).fill(2)), + ); + expect(await treasury.getTokenBalance(COLOR)).toEqual(AMOUNT * 2n); + expect(await treasury.getReceivedTotal(COLOR)).toEqual(AMOUNT * 2n); }); - it('should track balances per color independently', () => { - treasury._deposit(makeCoin(COLOR, AMOUNT)); - treasury._deposit(makeCoin(COLOR2, AMOUNT * 2n)); - expect(treasury.getTokenBalance(COLOR)).toEqual(AMOUNT); - expect(treasury.getTokenBalance(COLOR2)).toEqual(AMOUNT * 2n); + it('should track balances per color independently', async () => { + await treasury._deposit(makeCoin(COLOR, AMOUNT)); + await treasury._deposit(makeCoin(COLOR2, AMOUNT * 2n)); + expect(await treasury.getTokenBalance(COLOR)).toEqual(AMOUNT); + expect(await treasury.getTokenBalance(COLOR2)).toEqual(AMOUNT * 2n); }); - it('should allow zero value deposit', () => { - treasury._deposit(makeCoin(COLOR, 0n)); - expect(treasury.getTokenBalance(COLOR)).toEqual(0n); - expect(treasury.getReceivedTotal(COLOR)).toEqual(0n); + it('should allow zero value deposit', async () => { + await treasury._deposit(makeCoin(COLOR, 0n)); + expect(await treasury.getTokenBalance(COLOR)).toEqual(0n); + expect(await treasury.getReceivedTotal(COLOR)).toEqual(0n); }); - it('should maintain receivedMinusSent consistency', () => { - treasury._deposit(makeCoin(COLOR, AMOUNT)); - expect(treasury.getReceivedMinusSent(COLOR)).toEqual(AMOUNT); + it('should maintain receivedMinusSent consistency', async () => { + await treasury._deposit(makeCoin(COLOR, AMOUNT)); + expect(await treasury.getReceivedMinusSent(COLOR)).toEqual(AMOUNT); }); }); describe('_send', () => { - beforeEach(() => { - treasury._deposit(makeCoin(COLOR, AMOUNT)); + beforeEach(async () => { + await treasury._deposit(makeCoin(COLOR, AMOUNT)); }); - it('should send partial amount', () => { - treasury._send(Z_RECIPIENT, COLOR, 400n); - expect(treasury.getTokenBalance(COLOR)).toEqual(AMOUNT - 400n); + it('should send partial amount', async () => { + await treasury._send(Z_RECIPIENT, COLOR, 400n); + expect(await treasury.getTokenBalance(COLOR)).toEqual(AMOUNT - 400n); }); - it('should send full balance', () => { - treasury._send(Z_RECIPIENT, COLOR, AMOUNT); - expect(treasury.getTokenBalance(COLOR)).toEqual(0n); + it('should send full balance', async () => { + await treasury._send(Z_RECIPIENT, COLOR, AMOUNT); + expect(await treasury.getTokenBalance(COLOR)).toEqual(0n); }); - it('should track sent total', () => { - treasury._send(Z_RECIPIENT, COLOR, 400n); - expect(treasury.getSentTotal(COLOR)).toEqual(400n); + it('should track sent total', async () => { + await treasury._send(Z_RECIPIENT, COLOR, 400n); + expect(await treasury.getSentTotal(COLOR)).toEqual(400n); }); - it('should maintain receivedMinusSent after send', () => { - treasury._send(Z_RECIPIENT, COLOR, 400n); - expect(treasury.getReceivedMinusSent(COLOR)).toEqual(AMOUNT - 400n); + it('should maintain receivedMinusSent after send', async () => { + await treasury._send(Z_RECIPIENT, COLOR, 400n); + expect(await treasury.getReceivedMinusSent(COLOR)).toEqual(AMOUNT - 400n); }); - it('should fail with insufficient balance', () => { - expect(() => { - treasury._send(Z_RECIPIENT, COLOR, AMOUNT + 1n); - }).toThrow('ShieldedTreasury: coin value insufficient'); + it('should fail with insufficient balance', async () => { + await expect( + treasury._send(Z_RECIPIENT, COLOR, AMOUNT + 1n), + ).rejects.toThrow('ShieldedTreasury: coin value insufficient'); }); - it('should fail for unknown color', () => { - expect(() => { - treasury._send(Z_RECIPIENT, COLOR2, 1n); - }).toThrow('ShieldedTreasury: no balance'); + it('should fail for unknown color', async () => { + await expect(treasury._send(Z_RECIPIENT, COLOR2, 1n)).rejects.toThrow( + 'ShieldedTreasury: no balance', + ); }); }); describe('accounting consistency', () => { - it('should keep receivedMinusSent equal to balance', () => { - treasury._deposit(makeCoin(COLOR, 500n)); - treasury._send(Z_RECIPIENT, COLOR, 200n); - treasury._deposit(makeCoin(COLOR, 300n, new Uint8Array(32).fill(3))); - - const balance = treasury.getTokenBalance(COLOR); - const rms = treasury.getReceivedMinusSent(COLOR); + it('should keep receivedMinusSent equal to balance', async () => { + await treasury._deposit(makeCoin(COLOR, 500n)); + await treasury._send(Z_RECIPIENT, COLOR, 200n); + await treasury._deposit( + makeCoin(COLOR, 300n, new Uint8Array(32).fill(3)), + ); + + const balance = await treasury.getTokenBalance(COLOR); + const rms = await treasury.getReceivedMinusSent(COLOR); expect(balance).toEqual(600n); expect(rms).toEqual(600n); }); - it('should accumulate sent total across sends', () => { - treasury._deposit(makeCoin(COLOR, 1000n)); - treasury._send(Z_RECIPIENT, COLOR, 200n); - treasury._send(Z_RECIPIENT, COLOR, 300n); - expect(treasury.getSentTotal(COLOR)).toEqual(500n); + it('should accumulate sent total across sends', async () => { + await treasury._deposit(makeCoin(COLOR, 1000n)); + await treasury._send(Z_RECIPIENT, COLOR, 200n); + await treasury._send(Z_RECIPIENT, COLOR, 300n); + expect(await treasury.getSentTotal(COLOR)).toEqual(500n); }); }); }); diff --git a/contracts/src/multisig/test/Signer.test.ts b/contracts/src/multisig/test/Signer.test.ts index d915db18..da5af332 100644 --- a/contracts/src/multisig/test/Signer.test.ts +++ b/contracts/src/multisig/test/Signer.test.ts @@ -16,9 +16,9 @@ let contract: SignerSimulator; describe('Signer', () => { describe('when not initialized', () => { - beforeEach(() => { + beforeEach(async () => { const isNotInit = false; - contract = new SignerSimulator(SIGNERS, 0n, isNotInit); + contract = await SignerSimulator.create(SIGNERS, 0n, isNotInit); }); const circuitsRequiringInit: [string, unknown[]][] = [ @@ -28,349 +28,359 @@ describe('Signer', () => { ['getThreshold', []], ]; - it.each(circuitsRequiringInit)('%s should fail', (circuitName, args) => { - expect(() => { + it.each( + circuitsRequiringInit, + )('%s should fail', async (circuitName, args) => { + await expect( ( contract[circuitName as keyof SignerSimulator] as ( ...a: unknown[] - ) => unknown - )(...args); - }).toThrow('Signer: contract not initialized'); + ) => Promise + )(...args), + ).rejects.toThrow('Signer: contract not initialized'); }); - it('isSigner should succeed (no init guard)', () => { - expect(contract.isSigner(SIGNER)).toEqual(false); + it('isSigner should succeed (no init guard)', async () => { + expect(await contract.isSigner(SIGNER)).toEqual(false); }); }); describe('initialization', () => { - it('should fail with a threshold of zero', () => { - expect(() => { - new SignerSimulator(SIGNERS, 0n, IS_INIT); - }).toThrow('Signer: threshold must not be zero'); + it('should fail with a threshold of zero', async () => { + await expect( + SignerSimulator.create(SIGNERS, 0n, IS_INIT), + ).rejects.toThrow('Signer: threshold must not be zero'); }); - it('should fail when threshold exceeds signer count', () => { - expect(() => { - new SignerSimulator(SIGNERS, BigInt(SIGNERS.length) + 1n, IS_INIT); - }).toThrow('Signer: threshold exceeds signer count'); + it('should fail when threshold exceeds signer count', async () => { + await expect( + SignerSimulator.create(SIGNERS, BigInt(SIGNERS.length) + 1n, IS_INIT), + ).rejects.toThrow('Signer: threshold exceeds signer count'); }); - it('should fail with duplicate signers', () => { + it('should fail with duplicate signers', async () => { const duplicateSigners = [SIGNER, SIGNER, SIGNER2]; - expect(() => { - new SignerSimulator(duplicateSigners, THRESHOLD, IS_INIT); - }).toThrow('Signer: signer already active'); + await expect( + SignerSimulator.create(duplicateSigners, THRESHOLD, IS_INIT), + ).rejects.toThrow('Signer: signer already active'); }); - it('should initialize with threshold equal to signer count', () => { - const contract = new SignerSimulator( + it('should initialize with threshold equal to signer count', async () => { + const contract = await SignerSimulator.create( SIGNERS, BigInt(SIGNERS.length), IS_INIT, ); - expect(contract.getThreshold()).toEqual(BigInt(SIGNERS.length)); + expect(await contract.getThreshold()).toEqual(BigInt(SIGNERS.length)); }); - it('should initialize', () => { - expect(() => { - contract = new SignerSimulator(SIGNERS, THRESHOLD, IS_INIT); - }).not.toThrow(); + it('should initialize', async () => { + contract = await SignerSimulator.create(SIGNERS, THRESHOLD, IS_INIT); - expect(contract.getThreshold()).toEqual(THRESHOLD); - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length)); - expect(() => { - for (let i = 0; i < SIGNERS.length; i++) { - contract.assertSigner(SIGNERS[i]); - } - }).not.toThrow(); + expect(await contract.getThreshold()).toEqual(THRESHOLD); + expect(await contract.getSignerCount()).toEqual(BigInt(SIGNERS.length)); + for (let i = 0; i < SIGNERS.length; i++) { + await contract.assertSigner(SIGNERS[i]); + } }); - it('should fail when initialized twice', () => { - contract = new SignerSimulator(SIGNERS, THRESHOLD, IS_INIT); - expect(() => { - contract.initialize(SIGNERS, THRESHOLD); - }).toThrow('Signer: contract already initialized'); + it('should fail when initialized twice', async () => { + contract = await SignerSimulator.create(SIGNERS, THRESHOLD, IS_INIT); + await expect(contract.initialize(SIGNERS, THRESHOLD)).rejects.toThrow( + 'Signer: contract already initialized', + ); }); }); - beforeEach(() => { - contract = new SignerSimulator(SIGNERS, THRESHOLD, IS_INIT); + beforeEach(async () => { + contract = await SignerSimulator.create(SIGNERS, THRESHOLD, IS_INIT); }); describe('assertSigner', () => { - it('should pass with good signer', () => { - expect(() => contract.assertSigner(SIGNER)).not.toThrow(); + it('should pass with good signer', async () => { + await contract.assertSigner(SIGNER); }); - it('should fail with bad signer', () => { - expect(() => { - contract.assertSigner(OTHER); - }).toThrow('Signer: not a signer'); + it('should fail with bad signer', async () => { + await expect(contract.assertSigner(OTHER)).rejects.toThrow( + 'Signer: not a signer', + ); }); }); describe('assertThresholdMet', () => { - it('should pass when approvals equal threshold', () => { - expect(() => contract.assertThresholdMet(THRESHOLD)).not.toThrow(); + it('should pass when approvals equal threshold', async () => { + await contract.assertThresholdMet(THRESHOLD); }); - it('should pass when approvals exceed threshold', () => { - expect(() => contract.assertThresholdMet(THRESHOLD + 1n)).not.toThrow(); + it('should pass when approvals exceed threshold', async () => { + await contract.assertThresholdMet(THRESHOLD + 1n); }); - it('should fail when approvals are below threshold', () => { - expect(() => { - contract.assertThresholdMet(THRESHOLD - 1n); - }).toThrow('Signer: threshold not met'); + it('should fail when approvals are below threshold', async () => { + await expect(contract.assertThresholdMet(THRESHOLD - 1n)).rejects.toThrow( + 'Signer: threshold not met', + ); }); - it('should fail with zero approvals', () => { - expect(() => { - contract.assertThresholdMet(0n); - }).toThrow('Signer: threshold not met'); + it('should fail with zero approvals', async () => { + await expect(contract.assertThresholdMet(0n)).rejects.toThrow( + 'Signer: threshold not met', + ); }); }); describe('getSignerCount', () => { - it('should return the initial signer count', () => { - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length)); + it('should return the initial signer count', async () => { + expect(await contract.getSignerCount()).toEqual(BigInt(SIGNERS.length)); }); - it('should reflect additions', () => { - contract._addSigner(OTHER); - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length) + 1n); + it('should reflect additions', async () => { + await contract._addSigner(OTHER); + expect(await contract.getSignerCount()).toEqual( + BigInt(SIGNERS.length) + 1n, + ); }); - it('should reflect removals', () => { - contract._removeSigner(SIGNER3); - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length) - 1n); + it('should reflect removals', async () => { + await contract._removeSigner(SIGNER3); + expect(await contract.getSignerCount()).toEqual( + BigInt(SIGNERS.length) - 1n, + ); }); }); describe('getThreshold', () => { - it('should return the initial threshold', () => { - expect(contract.getThreshold()).toEqual(THRESHOLD); + it('should return the initial threshold', async () => { + expect(await contract.getThreshold()).toEqual(THRESHOLD); }); - it('should reflect _changeThreshold', () => { - contract._changeThreshold(3n); - expect(contract.getThreshold()).toEqual(3n); + it('should reflect _changeThreshold', async () => { + await contract._changeThreshold(3n); + expect(await contract.getThreshold()).toEqual(3n); }); - it('should reflect _setThreshold', () => { - contract._setThreshold(1n); - expect(contract.getThreshold()).toEqual(1n); + it('should reflect _setThreshold', async () => { + await contract._setThreshold(1n); + expect(await contract.getThreshold()).toEqual(1n); }); }); describe('isSigner', () => { - it('should return true for an active signer', () => { - expect(contract.isSigner(SIGNER)).toEqual(true); + it('should return true for an active signer', async () => { + expect(await contract.isSigner(SIGNER)).toEqual(true); }); - it('should return false for a non-signer', () => { - expect(contract.isSigner(OTHER)).toEqual(false); + it('should return false for a non-signer', async () => { + expect(await contract.isSigner(OTHER)).toEqual(false); }); }); describe('_addSigner', () => { - it('should add a new signer', () => { - contract._addSigner(OTHER); + it('should add a new signer', async () => { + await contract._addSigner(OTHER); - expect(contract.isSigner(OTHER)).toEqual(true); - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length) + 1n); + expect(await contract.isSigner(OTHER)).toEqual(true); + expect(await contract.getSignerCount()).toEqual( + BigInt(SIGNERS.length) + 1n, + ); }); - it('should fail when adding an existing signer', () => { - contract._addSigner(OTHER); + it('should fail when adding an existing signer', async () => { + await contract._addSigner(OTHER); - expect(() => { - contract._addSigner(OTHER); - }).toThrow('Signer: signer already active'); + await expect(contract._addSigner(OTHER)).rejects.toThrow( + 'Signer: signer already active', + ); }); - it('should add multiple new signers', () => { - contract._addSigner(OTHER); - contract._addSigner(OTHER2); + it('should add multiple new signers', async () => { + await contract._addSigner(OTHER); + await contract._addSigner(OTHER2); - expect(contract.isSigner(OTHER)).toEqual(true); - expect(contract.isSigner(OTHER2)).toEqual(true); - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length) + 2n); + expect(await contract.isSigner(OTHER)).toEqual(true); + expect(await contract.isSigner(OTHER2)).toEqual(true); + expect(await contract.getSignerCount()).toEqual( + BigInt(SIGNERS.length) + 2n, + ); }); - it('should allow re-adding a previously removed signer', () => { - expect(contract.isSigner(SIGNER)).toEqual(true); + it('should allow re-adding a previously removed signer', async () => { + expect(await contract.isSigner(SIGNER)).toEqual(true); - contract._removeSigner(SIGNER); - expect(contract.isSigner(SIGNER)).toEqual(false); + await contract._removeSigner(SIGNER); + expect(await contract.isSigner(SIGNER)).toEqual(false); - contract._addSigner(SIGNER); - expect(contract.isSigner(SIGNER)).toEqual(true); + await contract._addSigner(SIGNER); + expect(await contract.isSigner(SIGNER)).toEqual(true); }); }); describe('_removeSigner', () => { - it('should remove an existing signer', () => { - contract._removeSigner(SIGNER3); + it('should remove an existing signer', async () => { + await contract._removeSigner(SIGNER3); - expect(contract.isSigner(SIGNER3)).toEqual(false); - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length) - 1n); + expect(await contract.isSigner(SIGNER3)).toEqual(false); + expect(await contract.getSignerCount()).toEqual( + BigInt(SIGNERS.length) - 1n, + ); }); - it('should fail when removing a non-signer', () => { - expect(() => { - contract._removeSigner(OTHER); - }).toThrow('Signer: not a signer'); + it('should fail when removing a non-signer', async () => { + await expect(contract._removeSigner(OTHER)).rejects.toThrow( + 'Signer: not a signer', + ); }); - it('should fail when removal would breach threshold', () => { - contract._removeSigner(SIGNER3); + it('should fail when removal would breach threshold', async () => { + await contract._removeSigner(SIGNER3); - expect(() => { - contract._removeSigner(SIGNER2); - }).toThrow('Signer: removal would breach threshold'); + await expect(contract._removeSigner(SIGNER2)).rejects.toThrow( + 'Signer: removal would breach threshold', + ); }); - it('should allow removal after threshold is lowered', () => { - contract._changeThreshold(1n); - contract._removeSigner(SIGNER3); - contract._removeSigner(SIGNER2); + it('should allow removal after threshold is lowered', async () => { + await contract._changeThreshold(1n); + await contract._removeSigner(SIGNER3); + await contract._removeSigner(SIGNER2); - expect(contract.getSignerCount()).toEqual(1n); - expect(contract.isSigner(SIGNER)).toEqual(true); - expect(contract.isSigner(SIGNER2)).toEqual(false); - expect(contract.isSigner(SIGNER3)).toEqual(false); + expect(await contract.getSignerCount()).toEqual(1n); + expect(await contract.isSigner(SIGNER)).toEqual(true); + expect(await contract.isSigner(SIGNER2)).toEqual(false); + expect(await contract.isSigner(SIGNER3)).toEqual(false); }); - it('should keep signer count in sync after multiple add/remove operations', () => { - contract._addSigner(OTHER); - contract._addSigner(OTHER2); - contract._removeSigner(SIGNER3); - contract._removeSigner(OTHER); + it('should keep signer count in sync after multiple add/remove operations', async () => { + await contract._addSigner(OTHER); + await contract._addSigner(OTHER2); + await contract._removeSigner(SIGNER3); + await contract._removeSigner(OTHER); - expect(contract.getSignerCount()).toEqual(3n); - expect(contract.isSigner(SIGNER)).toEqual(true); - expect(contract.isSigner(SIGNER2)).toEqual(true); - expect(contract.isSigner(SIGNER3)).toEqual(false); - expect(contract.isSigner(OTHER)).toEqual(false); - expect(contract.isSigner(OTHER2)).toEqual(true); + expect(await contract.getSignerCount()).toEqual(3n); + expect(await contract.isSigner(SIGNER)).toEqual(true); + expect(await contract.isSigner(SIGNER2)).toEqual(true); + expect(await contract.isSigner(SIGNER3)).toEqual(false); + expect(await contract.isSigner(OTHER)).toEqual(false); + expect(await contract.isSigner(OTHER2)).toEqual(true); }); }); describe('_changeThreshold', () => { - it('should update the threshold', () => { - contract._changeThreshold(3n); + it('should update the threshold', async () => { + await contract._changeThreshold(3n); - expect(contract.getThreshold()).toEqual(3n); + expect(await contract.getThreshold()).toEqual(3n); }); - it('should allow lowering the threshold', () => { - contract._changeThreshold(1n); + it('should allow lowering the threshold', async () => { + await contract._changeThreshold(1n); - expect(contract.getThreshold()).toEqual(1n); + expect(await contract.getThreshold()).toEqual(1n); }); - it('should fail with a threshold of zero', () => { - expect(() => { - contract._changeThreshold(0n); - }).toThrow('Signer: threshold must not be zero'); + it('should fail with a threshold of zero', async () => { + await expect(contract._changeThreshold(0n)).rejects.toThrow( + 'Signer: threshold must not be zero', + ); }); - it('should fail when threshold exceeds signer count', () => { - expect(() => { - contract._changeThreshold(BigInt(SIGNERS.length) + 1n); - }).toThrow('Signer: threshold exceeds signer count'); + it('should fail when threshold exceeds signer count', async () => { + await expect( + contract._changeThreshold(BigInt(SIGNERS.length) + 1n), + ).rejects.toThrow('Signer: threshold exceeds signer count'); }); - it('should allow threshold equal to signer count', () => { - contract._changeThreshold(BigInt(SIGNERS.length)); + it('should allow threshold equal to signer count', async () => { + await contract._changeThreshold(BigInt(SIGNERS.length)); - expect(contract.getThreshold()).toEqual(BigInt(SIGNERS.length)); + expect(await contract.getThreshold()).toEqual(BigInt(SIGNERS.length)); }); - it('should reflect new threshold in assertThresholdMet', () => { - contract._changeThreshold(3n); + it('should reflect new threshold in assertThresholdMet', async () => { + await contract._changeThreshold(3n); - expect(() => { - contract.assertThresholdMet(2n); - }).toThrow('Signer: threshold not met'); + await expect(contract.assertThresholdMet(2n)).rejects.toThrow( + 'Signer: threshold not met', + ); - expect(() => contract.assertThresholdMet(3n)).not.toThrow(); + await contract.assertThresholdMet(3n); }); }); describe('_setThreshold', () => { - beforeEach(() => { + beforeEach(async () => { const isNotInit = false; - contract = new SignerSimulator(SIGNERS, 0n, isNotInit); + contract = await SignerSimulator.create(SIGNERS, 0n, isNotInit); }); - it('should have an empty state', () => { - expect(contract.getPublicState()._threshold).toEqual(0n); - expect(contract.getPublicState()._signerCount).toEqual(0n); - expect(contract.getPublicState()._signers.isEmpty()).toEqual(true); + it('should have an empty state', async () => { + expect((await contract.getPublicState())._threshold).toEqual(0n); + expect((await contract.getPublicState())._signerCount).toEqual(0n); + expect((await contract.getPublicState())._signers.isEmpty()).toEqual( + true, + ); }); - it('should set threshold without signers', () => { - expect(contract.getPublicState()._threshold).toEqual(0n); + it('should set threshold without signers', async () => { + expect((await contract.getPublicState())._threshold).toEqual(0n); - contract._setThreshold(2n); - expect(contract.getPublicState()._threshold).toEqual(2n); + await contract._setThreshold(2n); + expect((await contract.getPublicState())._threshold).toEqual(2n); }); - it('should set threshold multiple times', () => { - contract._setThreshold(2n); - contract._setThreshold(3n); - expect(contract.getPublicState()._threshold).toEqual(3n); + it('should set threshold multiple times', async () => { + await contract._setThreshold(2n); + await contract._setThreshold(3n); + expect((await contract.getPublicState())._threshold).toEqual(3n); }); - it('should fail with zero threshold', () => { - expect(() => { - contract._setThreshold(0n); - }).toThrow('Signer: threshold must not be zero'); + it('should fail with zero threshold', async () => { + await expect(contract._setThreshold(0n)).rejects.toThrow( + 'Signer: threshold must not be zero', + ); }); }); describe('custom setup flow when not initialized', () => { - beforeEach(() => { + beforeEach(async () => { const isNotInit = false; - contract = new SignerSimulator(SIGNERS, 0n, isNotInit); + contract = await SignerSimulator.create(SIGNERS, 0n, isNotInit); }); - it('should have no signers by default', () => { - expect(contract.getPublicState()._signerCount).toEqual(0n); - expect(contract.isSigner(SIGNER)).toEqual(false); + it('should have no signers by default', async () => { + expect((await contract.getPublicState())._signerCount).toEqual(0n); + expect(await contract.isSigner(SIGNER)).toEqual(false); }); - it('should have zero threshold by default', () => { - expect(contract.getPublicState()._threshold).toEqual(0n); + it('should have zero threshold by default', async () => { + expect((await contract.getPublicState())._threshold).toEqual(0n); }); - it('should allow adding signers then setting threshold', () => { - contract._addSigner(SIGNER); - contract._addSigner(SIGNER2); - contract._addSigner(SIGNER3); - contract._changeThreshold(2n); + it('should allow adding signers then setting threshold', async () => { + await contract._addSigner(SIGNER); + await contract._addSigner(SIGNER2); + await contract._addSigner(SIGNER3); + await contract._changeThreshold(2n); - expect(contract.getPublicState()._signerCount).toEqual(3n); - expect(contract.getPublicState()._threshold).toEqual(2n); - expect(contract.isSigner(SIGNER)).toEqual(true); + expect((await contract.getPublicState())._signerCount).toEqual(3n); + expect((await contract.getPublicState())._threshold).toEqual(2n); + expect(await contract.isSigner(SIGNER)).toEqual(true); }); - it('should allow setting threshold then adding signers to meet it', () => { - contract._setThreshold(2n); - contract._addSigner(SIGNER); - contract._addSigner(SIGNER2); + it('should allow setting threshold then adding signers to meet it', async () => { + await contract._setThreshold(2n); + await contract._addSigner(SIGNER); + await contract._addSigner(SIGNER2); - expect(contract.getPublicState()._signerCount).toEqual(2n); - expect(contract.getPublicState()._threshold).toEqual(2n); + expect((await contract.getPublicState())._signerCount).toEqual(2n); + expect((await contract.getPublicState())._threshold).toEqual(2n); }); - it('should fail _changeThreshold before signers are added', () => { - expect(() => { - contract._changeThreshold(2n); - }).toThrow('Signer: threshold exceeds signer count'); + it('should fail _changeThreshold before signers are added', async () => { + await expect(contract._changeThreshold(2n)).rejects.toThrow( + 'Signer: threshold exceeds signer count', + ); }); }); }); diff --git a/contracts/src/multisig/test/SignerManager.test.ts b/contracts/src/multisig/test/SignerManager.test.ts index 1ead55f7..9ecd2468 100644 --- a/contracts/src/multisig/test/SignerManager.test.ts +++ b/contracts/src/multisig/test/SignerManager.test.ts @@ -18,184 +18,186 @@ let contract: SignerManagerSimulator; describe('SigningManager', () => { describe('initialization', () => { - it('should fail with a threshold of zero', () => { - expect(() => { - new SignerManagerSimulator(SIGNERS, 0n); - }).toThrow('SignerManager: threshold must be > 0'); + it('should fail with a threshold of zero', async () => { + await expect(SignerManagerSimulator.create(SIGNERS, 0n)).rejects.toThrow( + 'SignerManager: threshold must be > 0', + ); }); - it('should fail with duplicate signers', () => { + it('should fail with duplicate signers', async () => { const duplicateSigners: SignerSet = [Z_SIGNER, Z_SIGNER, Z_SIGNER2]; - expect(() => { - new SignerManagerSimulator(duplicateSigners, THRESHOLD); - }).toThrow('SignerManager: signer already active'); + await expect( + SignerManagerSimulator.create(duplicateSigners, THRESHOLD), + ).rejects.toThrow('SignerManager: signer already active'); }); - it('should initialize', () => { - expect(() => { - contract = new SignerManagerSimulator(SIGNERS, THRESHOLD); - }).to.be.ok; + it('should initialize', async () => { + contract = await SignerManagerSimulator.create(SIGNERS, THRESHOLD); // Check thresh - expect(contract.getThreshold()).toEqual(THRESHOLD); + expect(await contract.getThreshold()).toEqual(THRESHOLD); // Check signers - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length)); - expect(() => { - for (let i = 0; i < SIGNERS.length; i++) { - contract.assertSigner(SIGNERS[i]); - } - }).to.be.ok; + expect(await contract.getSignerCount()).toEqual(BigInt(SIGNERS.length)); + for (let i = 0; i < SIGNERS.length; i++) { + await contract.assertSigner(SIGNERS[i]); + } }); }); - beforeEach(() => { - contract = new SignerManagerSimulator(SIGNERS, THRESHOLD); + beforeEach(async () => { + contract = await SignerManagerSimulator.create(SIGNERS, THRESHOLD); }); describe('assertSigner', () => { - it('should pass with good signer', () => { - expect(() => contract.assertSigner(Z_SIGNER)).not.toThrow(); + it('should pass with good signer', async () => { + await contract.assertSigner(Z_SIGNER); }); - it('should fail with bad signer', () => { - expect(() => { - contract.assertSigner(Z_OTHER); - }).toThrow('SignerManager: not a signer'); + it('should fail with bad signer', async () => { + await expect(contract.assertSigner(Z_OTHER)).rejects.toThrow( + 'SignerManager: not a signer', + ); }); }); describe('assertThresholdMet', () => { - it('should pass when approvals equal threshold', () => { - expect(() => contract.assertThresholdMet(THRESHOLD)).not.toThrow(); + it('should pass when approvals equal threshold', async () => { + await contract.assertThresholdMet(THRESHOLD); }); - it('should pass when approvals exceed threshold', () => { - expect(() => contract.assertThresholdMet(THRESHOLD + 1n)).not.toThrow(); + it('should pass when approvals exceed threshold', async () => { + await contract.assertThresholdMet(THRESHOLD + 1n); }); - it('should fail when approvals are below threshold', () => { - expect(() => { - contract.assertThresholdMet(THRESHOLD - 1n); - }).toThrow('SignerManager: threshold not met'); + it('should fail when approvals are below threshold', async () => { + await expect(contract.assertThresholdMet(THRESHOLD - 1n)).rejects.toThrow( + 'SignerManager: threshold not met', + ); }); - it('should fail with zero approvals', () => { - expect(() => { - contract.assertThresholdMet(0n); - }).toThrow('SignerManager: threshold not met'); + it('should fail with zero approvals', async () => { + await expect(contract.assertThresholdMet(0n)).rejects.toThrow( + 'SignerManager: threshold not met', + ); }); }); describe('isSigner', () => { - it('should return true for an active signer', () => { - expect(contract.isSigner(Z_SIGNER)).toEqual(true); + it('should return true for an active signer', async () => { + expect(await contract.isSigner(Z_SIGNER)).toEqual(true); }); - it('should return false for a non-signer', () => { - expect(contract.isSigner(Z_OTHER)).toEqual(false); + it('should return false for a non-signer', async () => { + expect(await contract.isSigner(Z_OTHER)).toEqual(false); }); }); describe('_addSigner', () => { - it('should add a new signer', () => { - contract._addSigner(Z_OTHER); + it('should add a new signer', async () => { + await contract._addSigner(Z_OTHER); - expect(contract.isSigner(Z_OTHER)).toEqual(true); - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length) + 1n); + expect(await contract.isSigner(Z_OTHER)).toEqual(true); + expect(await contract.getSignerCount()).toEqual( + BigInt(SIGNERS.length) + 1n, + ); }); - it('should fail when adding an existing signer', () => { - expect(() => { - contract._addSigner(Z_SIGNER); - }).toThrow('SignerManager: signer already active'); + it('should fail when adding an existing signer', async () => { + await expect(contract._addSigner(Z_SIGNER)).rejects.toThrow( + 'SignerManager: signer already active', + ); }); - it('should add multiple new signers', () => { - contract._addSigner(Z_OTHER); - contract._addSigner(Z_OTHER2); + it('should add multiple new signers', async () => { + await contract._addSigner(Z_OTHER); + await contract._addSigner(Z_OTHER2); - expect(contract.isSigner(Z_OTHER)).toEqual(true); - expect(contract.isSigner(Z_OTHER2)).toEqual(true); - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length) + 2n); + expect(await contract.isSigner(Z_OTHER)).toEqual(true); + expect(await contract.isSigner(Z_OTHER2)).toEqual(true); + expect(await contract.getSignerCount()).toEqual( + BigInt(SIGNERS.length) + 2n, + ); }); }); describe('_removeSigner', () => { - it('should remove an existing signer', () => { - contract._removeSigner(Z_SIGNER3); + it('should remove an existing signer', async () => { + await contract._removeSigner(Z_SIGNER3); - expect(contract.isSigner(Z_SIGNER3)).toEqual(false); - expect(contract.getSignerCount()).toEqual(BigInt(SIGNERS.length) - 1n); + expect(await contract.isSigner(Z_SIGNER3)).toEqual(false); + expect(await contract.getSignerCount()).toEqual( + BigInt(SIGNERS.length) - 1n, + ); }); - it('should fail when removing a non-signer', () => { - expect(() => { - contract._removeSigner(Z_OTHER); - }).toThrow('SignerManager: not a signer'); + it('should fail when removing a non-signer', async () => { + await expect(contract._removeSigner(Z_OTHER)).rejects.toThrow( + 'SignerManager: not a signer', + ); }); - it('should fail when removal would breach threshold', () => { + it('should fail when removal would breach threshold', async () => { // Remove one signer: count goes from 3 to 2, threshold is 2 — ok - contract._removeSigner(Z_SIGNER3); + await contract._removeSigner(Z_SIGNER3); // Remove another: count would go from 2 to 1, threshold is 2 — breach - expect(() => { - contract._removeSigner(Z_SIGNER2); - }).toThrow('SignerManager: removal would breach threshold'); + await expect(contract._removeSigner(Z_SIGNER2)).rejects.toThrow( + 'SignerManager: removal would breach threshold', + ); }); - it('should allow removal after threshold is lowered', () => { - contract._changeThreshold(1n); - contract._removeSigner(Z_SIGNER3); - contract._removeSigner(Z_SIGNER2); + it('should allow removal after threshold is lowered', async () => { + await contract._changeThreshold(1n); + await contract._removeSigner(Z_SIGNER3); + await contract._removeSigner(Z_SIGNER2); - expect(contract.getSignerCount()).toEqual(1n); - expect(contract.isSigner(Z_SIGNER)).toEqual(true); - expect(contract.isSigner(Z_SIGNER2)).toEqual(false); - expect(contract.isSigner(Z_SIGNER3)).toEqual(false); + expect(await contract.getSignerCount()).toEqual(1n); + expect(await contract.isSigner(Z_SIGNER)).toEqual(true); + expect(await contract.isSigner(Z_SIGNER2)).toEqual(false); + expect(await contract.isSigner(Z_SIGNER3)).toEqual(false); }); }); describe('_changeThreshold', () => { - it('should update the threshold', () => { - contract._changeThreshold(3n); + it('should update the threshold', async () => { + await contract._changeThreshold(3n); - expect(contract.getThreshold()).toEqual(3n); + expect(await contract.getThreshold()).toEqual(3n); }); - it('should allow lowering the threshold', () => { - contract._changeThreshold(1n); + it('should allow lowering the threshold', async () => { + await contract._changeThreshold(1n); - expect(contract.getThreshold()).toEqual(1n); + expect(await contract.getThreshold()).toEqual(1n); }); - it('should fail with a threshold of zero', () => { - expect(() => { - contract._changeThreshold(0n); - }).toThrow('SignerManager: threshold must be > 0'); + it('should fail with a threshold of zero', async () => { + await expect(contract._changeThreshold(0n)).rejects.toThrow( + 'SignerManager: threshold must be > 0', + ); }); - it('should fail when threshold exceeds signer count', () => { - expect(() => { - contract._changeThreshold(BigInt(SIGNERS.length) + 1n); - }).toThrow('SignerManager: threshold exceeds signer count'); + it('should fail when threshold exceeds signer count', async () => { + await expect( + contract._changeThreshold(BigInt(SIGNERS.length) + 1n), + ).rejects.toThrow('SignerManager: threshold exceeds signer count'); }); - it('should allow threshold equal to signer count', () => { - contract._changeThreshold(BigInt(SIGNERS.length)); + it('should allow threshold equal to signer count', async () => { + await contract._changeThreshold(BigInt(SIGNERS.length)); - expect(contract.getThreshold()).toEqual(BigInt(SIGNERS.length)); + expect(await contract.getThreshold()).toEqual(BigInt(SIGNERS.length)); }); - it('should reflect new threshold in assertThresholdMet', () => { - contract._changeThreshold(3n); + it('should reflect new threshold in assertThresholdMet', async () => { + await contract._changeThreshold(3n); - expect(() => { - contract.assertThresholdMet(2n); - }).toThrow('SignerManager: threshold not met'); + await expect(contract.assertThresholdMet(2n)).rejects.toThrow( + 'SignerManager: threshold not met', + ); - expect(() => contract.assertThresholdMet(3n)).not.toThrow(); + await contract.assertThresholdMet(3n); }); }); }); diff --git a/contracts/src/multisig/test/presets/ForwarderPrivate.test.ts b/contracts/src/multisig/test/presets/ForwarderPrivate.test.ts index 75a21259..ba87b3fd 100644 --- a/contracts/src/multisig/test/presets/ForwarderPrivate.test.ts +++ b/contracts/src/multisig/test/presets/ForwarderPrivate.test.ts @@ -26,25 +26,25 @@ function commitment(parent: Uint8Array, opSecret: Uint8Array): Uint8Array { } describe('ForwarderPrivate preset', () => { - it('should store the parentCommitment passed to the constructor', () => { + it('should store the parentCommitment passed to the constructor', async () => { const c = commitment(PARENT_BYTES, OP_SECRET); - const fwd = new ForwarderPrivateSimulator(c); - expect(fwd.getParentCommitment()).toEqual(c); + const fwd = await ForwarderPrivateSimulator.create(c); + expect(await fwd.getParentCommitment()).toEqual(c); }); - it('should expose deposit and forward to _deposit', () => { - const fwd = new ForwarderPrivateSimulator( + it('should expose deposit and forward to _deposit', async () => { + const fwd = await ForwarderPrivateSimulator.create( commitment(PARENT_BYTES, OP_SECRET), ); - expect(() => fwd.deposit(makeCoin(COLOR, AMOUNT))).not.toThrow(); + await fwd.deposit(makeCoin(COLOR, AMOUNT)); }); - it('should expose drain and forward to _drain', () => { - const fwd = new ForwarderPrivateSimulator( + it('should expose drain and forward to _drain', async () => { + const fwd = await ForwarderPrivateSimulator.create( commitment(PARENT_BYTES, OP_SECRET), ); - fwd.deposit(makeCoin(COLOR, AMOUNT)); - const result = fwd.drain( + await fwd.deposit(makeCoin(COLOR, AMOUNT)); + const result = await fwd.drain( makeQualifiedCoin(COLOR, AMOUNT, 0n), key(PARENT_BYTES), OP_SECRET, @@ -59,16 +59,16 @@ describe('ForwarderPrivate preset', () => { expect(c1).toEqual(c2); }); - it('should propagate the zero-commitment guard from the module', () => { - expect(() => new ForwarderPrivateSimulator(new Uint8Array(32))).toThrow( - 'ForwarderPrivate: zero commitment', - ); + it('should propagate the zero-commitment guard from the module', async () => { + await expect( + ForwarderPrivateSimulator.create(new Uint8Array(32)), + ).rejects.toThrow('ForwarderPrivate: zero commitment'); }); - it('should expose the public ledger state', () => { - const fwd = new ForwarderPrivateSimulator( + it('should expose the public ledger state', async () => { + const fwd = await ForwarderPrivateSimulator.create( commitment(PARENT_BYTES, OP_SECRET), ); - expect(fwd.getPublicState()).toBeDefined(); + expect(await fwd.getPublicState()).toBeDefined(); }); }); diff --git a/contracts/src/multisig/test/presets/ForwarderShielded.test.ts b/contracts/src/multisig/test/presets/ForwarderShielded.test.ts index 1f034b20..14445072 100644 --- a/contracts/src/multisig/test/presets/ForwarderShielded.test.ts +++ b/contracts/src/multisig/test/presets/ForwarderShielded.test.ts @@ -16,26 +16,26 @@ function makeCoin(color: Uint8Array, value: bigint) { } describe('ForwarderShielded preset', () => { - it('should store the parent passed to the constructor in the left arm', () => { - const fwd = new ForwarderShieldedSimulator(PARENT); - const parent = fwd.getParent(); + it('should store the parent passed to the constructor in the left arm', async () => { + const fwd = await ForwarderShieldedSimulator.create(PARENT); + const parent = await fwd.getParent(); expect(parent.is_left).toBe(true); expect(parent.left).toEqual(PARENT); }); - it('should expose deposit and forward to _deposit', () => { - const fwd = new ForwarderShieldedSimulator(PARENT); - expect(() => fwd.deposit(makeCoin(COLOR, AMOUNT))).not.toThrow(); + it('should expose deposit and forward to _deposit', async () => { + const fwd = await ForwarderShieldedSimulator.create(PARENT); + await fwd.deposit(makeCoin(COLOR, AMOUNT)); }); - it('should propagate the zero-parent guard from the module', () => { - expect(() => new ForwarderShieldedSimulator(ZERO_KEY)).toThrow( + it('should propagate the zero-parent guard from the module', async () => { + await expect(ForwarderShieldedSimulator.create(ZERO_KEY)).rejects.toThrow( 'ForwarderShielded: zero parent', ); }); - it('should expose the public ledger state', () => { - const fwd = new ForwarderShieldedSimulator(PARENT); - expect(fwd.getPublicState()).toBeDefined(); + it('should expose the public ledger state', async () => { + const fwd = await ForwarderShieldedSimulator.create(PARENT); + expect(await fwd.getPublicState()).toBeDefined(); }); }); diff --git a/contracts/src/multisig/test/presets/ForwarderUnshielded.test.ts b/contracts/src/multisig/test/presets/ForwarderUnshielded.test.ts index 5d81cda3..0f3ade64 100644 --- a/contracts/src/multisig/test/presets/ForwarderUnshielded.test.ts +++ b/contracts/src/multisig/test/presets/ForwarderUnshielded.test.ts @@ -12,26 +12,26 @@ const COLOR = new Uint8Array(32).fill(1); const AMOUNT = 1000n; describe('ForwarderUnshielded preset', () => { - it('should store the parent passed to the constructor in the right arm', () => { - const fwd = new ForwarderUnshieldedSimulator(PARENT); - const parent = fwd.getParent(); + it('should store the parent passed to the constructor in the right arm', async () => { + const fwd = await ForwarderUnshieldedSimulator.create(PARENT); + const parent = await fwd.getParent(); expect(parent.is_left).toBe(false); expect(parent.right).toEqual(PARENT); }); - it('should expose deposit and forward to _deposit', () => { - const fwd = new ForwarderUnshieldedSimulator(PARENT); - expect(() => fwd.deposit(COLOR, AMOUNT)).not.toThrow(); + it('should expose deposit and forward to _deposit', async () => { + const fwd = await ForwarderUnshieldedSimulator.create(PARENT); + await fwd.deposit(COLOR, AMOUNT); }); - it('should propagate the zero-parent guard from the module', () => { - expect(() => new ForwarderUnshieldedSimulator(ZERO_ADDR)).toThrow( - 'ForwarderUnshielded: zero parent', - ); + it('should propagate the zero-parent guard from the module', async () => { + await expect( + ForwarderUnshieldedSimulator.create(ZERO_ADDR), + ).rejects.toThrow('ForwarderUnshielded: zero parent'); }); - it('should expose the public ledger state', () => { - const fwd = new ForwarderUnshieldedSimulator(PARENT); - expect(fwd.getPublicState()).toBeDefined(); + it('should expose the public ledger state', async () => { + const fwd = await ForwarderUnshieldedSimulator.create(PARENT); + expect(await fwd.getPublicState()).toBeDefined(); }); }); diff --git a/contracts/src/multisig/test/simulators/MockForwarderPrivateSimulator.ts b/contracts/src/multisig/test/simulators/MockForwarderPrivateSimulator.ts index 3c3ec47f..7f3e2db7 100644 --- a/contracts/src/multisig/test/simulators/MockForwarderPrivateSimulator.ts +++ b/contracts/src/multisig/test/simulators/MockForwarderPrivateSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { ledger, @@ -31,18 +31,23 @@ const MockForwarderPrivateSimulatorBase = createSimulator< contractArgs: (parentCommitment, isInit) => [parentCommitment, isInit], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => emptyWitnesses(), + artifactName: 'MockForwarderPrivate', }); export class MockForwarderPrivateSimulator extends MockForwarderPrivateSimulatorBase { - constructor( + static async create( parentCommitment: Uint8Array, isInit: boolean, - options: BaseSimulatorOptions< + options: SimulatorOptions< EmptyPrivateState, ReturnType > = {}, - ) { - super([parentCommitment, isInit], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [parentCommitment, isInit], + options, + ) as Promise; } public static calculateParentCommitment( @@ -52,7 +57,7 @@ export class MockForwarderPrivateSimulator extends MockForwarderPrivateSimulator return pureCircuits.calculateParentCommitment(parentAddr, opSecret); } - public deposit(coin: ShieldedCoinInfo) { + public deposit(coin: ShieldedCoinInfo): Promise<[]> { return this.circuits.impure.deposit(coin); } @@ -61,11 +66,11 @@ export class MockForwarderPrivateSimulator extends MockForwarderPrivateSimulator parent: ZswapCoinPublicKey, opSecret: Uint8Array, value: bigint, - ): ShieldedSendResult { + ): Promise { return this.circuits.impure.drain(coin, parent, opSecret, value); } - public getParentCommitment(): Uint8Array { + public getParentCommitment(): Promise { return this.circuits.impure.getParentCommitment(); } } diff --git a/contracts/src/multisig/test/simulators/MockForwarderShieldedSimulator.ts b/contracts/src/multisig/test/simulators/MockForwarderShieldedSimulator.ts index 81771494..dacc6336 100644 --- a/contracts/src/multisig/test/simulators/MockForwarderShieldedSimulator.ts +++ b/contracts/src/multisig/test/simulators/MockForwarderShieldedSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -30,25 +30,30 @@ const MockForwarderShieldedSimulatorBase = createSimulator< contractArgs: (parent, isInit) => [parent, isInit], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => emptyWitnesses(), + artifactName: 'MockForwarderShielded', }); export class MockForwarderShieldedSimulator extends MockForwarderShieldedSimulatorBase { - constructor( + static async create( parent: ZswapCoinPublicKey, isInit: boolean, - options: BaseSimulatorOptions< + options: SimulatorOptions< EmptyPrivateState, ReturnType > = {}, - ) { - super([parent, isInit], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [parent, isInit], + options, + ) as Promise; } - public deposit(coin: ShieldedCoinInfo) { + public deposit(coin: ShieldedCoinInfo): Promise<[]> { return this.circuits.impure.deposit(coin); } - public getParent(): Either { + public getParent(): Promise> { return this.circuits.impure.getParent(); } } diff --git a/contracts/src/multisig/test/simulators/MockForwarderUnshieldedSimulator.ts b/contracts/src/multisig/test/simulators/MockForwarderUnshieldedSimulator.ts index 82fa0eae..ffc3ba95 100644 --- a/contracts/src/multisig/test/simulators/MockForwarderUnshieldedSimulator.ts +++ b/contracts/src/multisig/test/simulators/MockForwarderUnshieldedSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -29,25 +29,30 @@ const MockForwarderUnshieldedSimulatorBase = createSimulator< contractArgs: (parent, isInit) => [parent, isInit], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => emptyWitnesses(), + artifactName: 'MockForwarderUnshielded', }); export class MockForwarderUnshieldedSimulator extends MockForwarderUnshieldedSimulatorBase { - constructor( + static async create( parent: UserAddress, isInit: boolean, - options: BaseSimulatorOptions< + options: SimulatorOptions< EmptyPrivateState, ReturnType > = {}, - ) { - super([parent, isInit], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [parent, isInit], + options, + ) as Promise; } - public deposit(color: Uint8Array, amount: bigint) { + public deposit(color: Uint8Array, amount: bigint): Promise<[]> { return this.circuits.impure.deposit(color, amount); } - public getParent(): Either { + public getParent(): Promise> { return this.circuits.impure.getParent(); } } diff --git a/contracts/src/multisig/test/simulators/ProposalManagerSimulator.ts b/contracts/src/multisig/test/simulators/ProposalManagerSimulator.ts index f43e676a..98c97436 100644 --- a/contracts/src/multisig/test/simulators/ProposalManagerSimulator.ts +++ b/contracts/src/multisig/test/simulators/ProposalManagerSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { ledger, @@ -35,16 +35,18 @@ const ProposalManagerSimulatorBase = createSimulator< contractArgs: () => [], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => ProposalManagerWitnesses(), + artifactName: 'MockProposalManager', }); export class ProposalManagerSimulator extends ProposalManagerSimulatorBase { - constructor( - options: BaseSimulatorOptions< + static async create( + options: SimulatorOptions< ProposalManagerPrivateState, ReturnType > = {}, - ) { - super([], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create([], options) as Promise; } // Pure circuits (recipient helpers) @@ -77,11 +79,11 @@ export class ProposalManagerSimulator extends ProposalManagerSimulatorBase { } // Guards - public assertProposalExists(id: bigint) { + public assertProposalExists(id: bigint): Promise<[]> { return this.circuits.impure.assertProposalExists(id); } - public assertProposalActive(id: bigint) { + public assertProposalActive(id: bigint): Promise<[]> { return this.circuits.impure.assertProposalActive(id); } @@ -90,36 +92,36 @@ export class ProposalManagerSimulator extends ProposalManagerSimulatorBase { to: Recipient, color: Uint8Array, amount: bigint, - ): bigint { + ): Promise { return this.circuits.impure._createProposal(to, color, amount); } - public _cancelProposal(id: bigint) { + public _cancelProposal(id: bigint): Promise<[]> { return this.circuits.impure._cancelProposal(id); } - public _markExecuted(id: bigint) { + public _markExecuted(id: bigint): Promise<[]> { return this.circuits.impure._markExecuted(id); } // View - public getProposal(id: bigint): Proposal { + public getProposal(id: bigint): Promise { return this.circuits.impure.getProposal(id); } - public getProposalRecipient(id: bigint): Recipient { + public getProposalRecipient(id: bigint): Promise { return this.circuits.impure.getProposalRecipient(id); } - public getProposalAmount(id: bigint): bigint { + public getProposalAmount(id: bigint): Promise { return this.circuits.impure.getProposalAmount(id); } - public getProposalColor(id: bigint): Uint8Array { + public getProposalColor(id: bigint): Promise { return this.circuits.impure.getProposalColor(id); } - public getProposalStatus(id: bigint): number { + public getProposalStatus(id: bigint): Promise { return this.circuits.impure.getProposalStatus(id); } } diff --git a/contracts/src/multisig/test/simulators/ShieldedMultiSigSimulator.ts b/contracts/src/multisig/test/simulators/ShieldedMultiSigSimulator.ts index f881384b..a58035f4 100644 --- a/contracts/src/multisig/test/simulators/ShieldedMultiSigSimulator.ts +++ b/contracts/src/multisig/test/simulators/ShieldedMultiSigSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type Ledger, @@ -48,22 +48,27 @@ const ShieldedMultiSigSimulatorBase = createSimulator< contractArgs: (signers, thresh) => [signers, thresh], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => ShieldedMultiSigWitnesses(), + artifactName: 'ShieldedMultiSig', }); export class ShieldedMultiSigSimulator extends ShieldedMultiSigSimulatorBase { - constructor( + static async create( signers: EitherPKAddress[], thresh: bigint, - options: BaseSimulatorOptions< + options: SimulatorOptions< ShieldedMultiSigPrivateState, ReturnType > = {}, - ) { - super([signers, thresh], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [signers, thresh], + options, + ) as Promise; } // Deposit - public deposit(coin: ShieldedCoinInfo) { + public deposit(coin: ShieldedCoinInfo): Promise<[]> { return this.circuits.impure.deposit(coin); } @@ -72,19 +77,19 @@ export class ShieldedMultiSigSimulator extends ShieldedMultiSigSimulatorBase { to: Recipient, color: Uint8Array, amount: bigint, - ): bigint { + ): Promise { return this.circuits.impure.createShieldedProposal(to, color, amount); } - public approveProposal(id: bigint) { + public approveProposal(id: bigint): Promise<[]> { return this.circuits.impure.approveProposal(id); } - public revokeApproval(id: bigint) { + public revokeApproval(id: bigint): Promise<[]> { return this.circuits.impure.revokeApproval(id); } - public executeShieldedProposal(id: bigint): ShieldedSendResult { + public executeShieldedProposal(id: bigint): Promise { return this.circuits.impure.executeShieldedProposal(id); } @@ -92,67 +97,67 @@ export class ShieldedMultiSigSimulator extends ShieldedMultiSigSimulatorBase { public isProposalApprovedBySigner( id: bigint, signer: EitherPKAddress, - ): boolean { + ): Promise { return this.circuits.impure.isProposalApprovedBySigner(id, signer); } - public getApprovalCount(id: bigint): bigint { + public getApprovalCount(id: bigint): Promise { return this.circuits.impure.getApprovalCount(id); } // View - Proposals - public getProposal(id: bigint): Proposal { + public getProposal(id: bigint): Promise { return this.circuits.impure.getProposal(id); } - public getProposalRecipient(id: bigint): Recipient { + public getProposalRecipient(id: bigint): Promise { return this.circuits.impure.getProposalRecipient(id); } - public getProposalAmount(id: bigint): bigint { + public getProposalAmount(id: bigint): Promise { return this.circuits.impure.getProposalAmount(id); } - public getProposalColor(id: bigint): Uint8Array { + public getProposalColor(id: bigint): Promise { return this.circuits.impure.getProposalColor(id); } - public getProposalStatus(id: bigint): number { + public getProposalStatus(id: bigint): Promise { return this.circuits.impure.getProposalStatus(id); } // View - Treasury - public getTokenBalance(color: Uint8Array): bigint { + public getTokenBalance(color: Uint8Array): Promise { return this.circuits.impure.getTokenBalance(color); } - public getReceivedTotal(color: Uint8Array): bigint { + public getReceivedTotal(color: Uint8Array): Promise { return this.circuits.impure.getReceivedTotal(color); } - public getSentTotal(color: Uint8Array): bigint { + public getSentTotal(color: Uint8Array): Promise { return this.circuits.impure.getSentTotal(color); } - public getReceivedMinusSent(color: Uint8Array): bigint { + public getReceivedMinusSent(color: Uint8Array): Promise { return this.circuits.impure.getReceivedMinusSent(color); } // View - Signers - public getSignerCount(): bigint { + public getSignerCount(): Promise { return this.circuits.impure.getSignerCount(); } - public getThreshold(): bigint { + public getThreshold(): Promise { return this.circuits.impure.getThreshold(); } - public isSigner(account: EitherPKAddress): boolean { + public isSigner(account: EitherPKAddress): Promise { return this.circuits.impure.isSigner(account); } // Ledger access - public getLedger(): Ledger { + public getLedger(): Promise { return this.getPublicState(); } } diff --git a/contracts/src/multisig/test/simulators/ShieldedMultiSigV2Simulator.ts b/contracts/src/multisig/test/simulators/ShieldedMultiSigV2Simulator.ts index 3e091fa5..c03078bd 100644 --- a/contracts/src/multisig/test/simulators/ShieldedMultiSigV2Simulator.ts +++ b/contracts/src/multisig/test/simulators/ShieldedMultiSigV2Simulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type Ledger, @@ -49,19 +49,24 @@ const ShieldedMultiSigV2SimulatorBase = createSimulator< ], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => ShieldedMultiSigV2Witnesses(), + artifactName: 'ShieldedMultiSigV2', }); export class ShieldedMultiSigV2Simulator extends ShieldedMultiSigV2SimulatorBase { - constructor( + static async create( instanceSalt: Uint8Array, signerCommitments: Uint8Array[], thresh: bigint, - options: BaseSimulatorOptions< + options: SimulatorOptions< ShieldedMultiSigV2PrivateState, ReturnType > = {}, - ) { - super([instanceSalt, signerCommitments, thresh], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [instanceSalt, signerCommitments, thresh], + options, + ) as Promise; } public static calculateSignerId( @@ -71,7 +76,7 @@ export class ShieldedMultiSigV2Simulator extends ShieldedMultiSigV2SimulatorBase return pureCircuits._calculateSignerId(pk, salt); } - public deposit(coin: ShieldedCoinInfo) { + public deposit(coin: ShieldedCoinInfo): Promise<[]> { return this.circuits.impure.deposit(coin); } @@ -81,27 +86,27 @@ export class ShieldedMultiSigV2Simulator extends ShieldedMultiSigV2SimulatorBase coin: QualifiedShieldedCoinInfo, pubkeys: Uint8Array[], signatures: Uint8Array[], - ): ShieldedSendResult { + ): Promise { return this.circuits.impure.execute(to, amount, coin, pubkeys, signatures); } - public getNonce(): bigint { + public getNonce(): Promise { return this.circuits.impure.getNonce(); } - public getSignerCount(): bigint { + public getSignerCount(): Promise { return this.circuits.impure.getSignerCount(); } - public getThreshold(): bigint { + public getThreshold(): Promise { return this.circuits.impure.getThreshold(); } - public isSigner(commitment: Uint8Array): boolean { + public isSigner(commitment: Uint8Array): Promise { return this.circuits.impure.isSigner(commitment); } - public getLedger(): Ledger { + public getLedger(): Promise { return this.getPublicState(); } } diff --git a/contracts/src/multisig/test/simulators/ShieldedMultiSigV3Simulator.ts b/contracts/src/multisig/test/simulators/ShieldedMultiSigV3Simulator.ts index bbd5bc28..afba649f 100644 --- a/contracts/src/multisig/test/simulators/ShieldedMultiSigV3Simulator.ts +++ b/contracts/src/multisig/test/simulators/ShieldedMultiSigV3Simulator.ts @@ -1,8 +1,10 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { + type ContractAddress, + type Either, ledger, pureCircuits, Contract as ShieldedMultiSigV3Contract, @@ -38,26 +40,31 @@ const ShieldedMultiSigV3SimulatorBase = createSimulator< ) => [instanceSalt, initCoinNonce, tokenDomain, signerCommitments], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => ShieldedMultiSigV3Witnesses(), + artifactName: 'ShieldedMultiSigV3', }); export class ShieldedMultiSigV3Simulator extends ShieldedMultiSigV3SimulatorBase { - constructor( + static async create( instanceSalt: Uint8Array, initCoinNonce: Uint8Array, tokenDomain: Uint8Array, signerCommitments: Uint8Array[], - options: BaseSimulatorOptions< + options: SimulatorOptions< ShieldedMultiSigV3PrivateState, ReturnType > = {}, - ) { - super( + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( [instanceSalt, initCoinNonce, tokenDomain, signerCommitments], options, - ); + ) as Promise; } - public _calculateSignerId(pk: Uint8Array, salt: Uint8Array): Uint8Array { + public _calculateSignerId( + pk: Uint8Array, + salt: Uint8Array, + ): Promise { return this.circuits.pure._calculateSignerId(pk, salt); } @@ -66,7 +73,7 @@ export class ShieldedMultiSigV3Simulator extends ShieldedMultiSigV3SimulatorBase recipient: Either, pubkeys: Uint8Array[], signatures: Uint8Array[], - ) { + ): Promise<[]> { return this.circuits.impure.mint(amount, recipient, pubkeys, signatures); } @@ -80,31 +87,31 @@ export class ShieldedMultiSigV3Simulator extends ShieldedMultiSigV3SimulatorBase amount: bigint, pubkeys: Uint8Array[], signatures: Uint8Array[], - ) { + ): Promise<[]> { return this.circuits.impure.burn(coin, amount, pubkeys, signatures); } - public getNonce(): bigint { + public getNonce(): Promise { return this.circuits.impure.getNonce(); } - public getTokenDomain(): Uint8Array { + public getTokenDomain(): Promise { return this.circuits.impure.getTokenDomain(); } - public getTokenType(): Uint8Array { + public getTokenType(): Promise { return this.circuits.impure.getTokenType(); } - public getSignerCount(): bigint { + public getSignerCount(): Promise { return this.circuits.impure.getSignerCount(); } - public getThreshold(): bigint { + public getThreshold(): Promise { return this.circuits.impure.getThreshold(); } - public isSigner(commitment: Uint8Array): boolean { + public isSigner(commitment: Uint8Array): Promise { return this.circuits.impure.isSigner(commitment); } } diff --git a/contracts/src/multisig/test/simulators/ShieldedTreasurySimulator.ts b/contracts/src/multisig/test/simulators/ShieldedTreasurySimulator.ts index 6cbfb61c..210972d2 100644 --- a/contracts/src/multisig/test/simulators/ShieldedTreasurySimulator.ts +++ b/contracts/src/multisig/test/simulators/ShieldedTreasurySimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { ledger, @@ -32,19 +32,21 @@ const ShieldedTreasurySimulatorBase = createSimulator< contractArgs: () => [], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => ShieldedTreasuryWitnesses(), + artifactName: 'MockShieldedTreasury', }); export class ShieldedTreasurySimulator extends ShieldedTreasurySimulatorBase { - constructor( - options: BaseSimulatorOptions< + static async create( + options: SimulatorOptions< ShieldedTreasuryPrivateState, ReturnType > = {}, - ) { - super([], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create([], options) as Promise; } - public _deposit(coin: ShieldedCoinInfo) { + public _deposit(coin: ShieldedCoinInfo): Promise<[]> { return this.circuits.impure._deposit(coin); } @@ -56,23 +58,23 @@ export class ShieldedTreasurySimulator extends ShieldedTreasurySimulatorBase { }, color: Uint8Array, amount: bigint, - ): ShieldedSendResult { + ): Promise { return this.circuits.impure._send(recipient, color, amount); } - public getTokenBalance(color: Uint8Array): bigint { + public getTokenBalance(color: Uint8Array): Promise { return this.circuits.impure.getTokenBalance(color); } - public getReceivedTotal(color: Uint8Array): bigint { + public getReceivedTotal(color: Uint8Array): Promise { return this.circuits.impure.getReceivedTotal(color); } - public getSentTotal(color: Uint8Array): bigint { + public getSentTotal(color: Uint8Array): Promise { return this.circuits.impure.getSentTotal(color); } - public getReceivedMinusSent(color: Uint8Array): bigint { + public getReceivedMinusSent(color: Uint8Array): Promise { return this.circuits.impure.getReceivedMinusSent(color); } } diff --git a/contracts/src/multisig/test/simulators/SignerManagerSimulator.ts b/contracts/src/multisig/test/simulators/SignerManagerSimulator.ts index 151aee48..be5ee9aa 100644 --- a/contracts/src/multisig/test/simulators/SignerManagerSimulator.ts +++ b/contracts/src/multisig/test/simulators/SignerManagerSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -43,54 +43,65 @@ const SignerManagerSimulatorBase = createSimulator< contractArgs: (signers, thresh) => [signers, thresh], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => SignerManagerWitnesses(), + artifactName: 'MockSignerManager', }); /** * SignerManager Simulator */ export class SignerManagerSimulator extends SignerManagerSimulatorBase { - constructor( + static async create( signers: SignerSet, thresh: bigint, - options: BaseSimulatorOptions< + options: SimulatorOptions< SignerManagerPrivateState, ReturnType > = {}, - ) { - super([signers, thresh], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [signers, thresh], + options, + ) as Promise; } - public assertSigner(caller: Either) { + public assertSigner( + caller: Either, + ): Promise<[]> { return this.circuits.impure.assertSigner(caller); } - public assertThresholdMet(approvalCount: bigint) { + public assertThresholdMet(approvalCount: bigint): Promise<[]> { return this.circuits.impure.assertThresholdMet(approvalCount); } - public getSignerCount(): bigint { + public getSignerCount(): Promise { return this.circuits.impure.getSignerCount(); } - public getThreshold(): bigint { + public getThreshold(): Promise { return this.circuits.impure.getThreshold(); } public isSigner( account: Either, - ): boolean { + ): Promise { return this.circuits.impure.isSigner(account); } - public _addSigner(signer: Either) { + public _addSigner( + signer: Either, + ): Promise<[]> { return this.circuits.impure._addSigner(signer); } - public _removeSigner(signer: Either) { + public _removeSigner( + signer: Either, + ): Promise<[]> { return this.circuits.impure._removeSigner(signer); } - public _changeThreshold(newThreshold: bigint) { + public _changeThreshold(newThreshold: bigint): Promise<[]> { return this.circuits.impure._changeThreshold(newThreshold); } } diff --git a/contracts/src/multisig/test/simulators/SignerSimulator.ts b/contracts/src/multisig/test/simulators/SignerSimulator.ts index 37d0fa82..ef3f3948 100644 --- a/contracts/src/multisig/test/simulators/SignerSimulator.ts +++ b/contracts/src/multisig/test/simulators/SignerSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { ledger, @@ -32,61 +32,66 @@ const SignerSimulatorBase = createSimulator< contractArgs: (signers, thresh, isInit) => [signers, thresh, isInit], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => SignerWitnesses(), + artifactName: 'MockSigner', }); /** * Signer Simulator */ export class SignerSimulator extends SignerSimulatorBase { - constructor( + static async create( signers: Uint8Array[], thresh: bigint, isInit: boolean, - options: BaseSimulatorOptions< + options: SimulatorOptions< SignerPrivateState, ReturnType > = {}, - ) { - super([signers, thresh, isInit], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [signers, thresh, isInit], + options, + ) as Promise; } - public initialize(signers: Uint8Array[], thresh: bigint) { + public initialize(signers: Uint8Array[], thresh: bigint): Promise<[]> { return this.circuits.impure.initialize(signers, thresh); } - public assertSigner(caller: Uint8Array) { + public assertSigner(caller: Uint8Array): Promise<[]> { return this.circuits.impure.assertSigner(caller); } - public assertThresholdMet(approvalCount: bigint) { + public assertThresholdMet(approvalCount: bigint): Promise<[]> { return this.circuits.impure.assertThresholdMet(approvalCount); } - public getSignerCount(): bigint { + public getSignerCount(): Promise { return this.circuits.impure.getSignerCount(); } - public getThreshold(): bigint { + public getThreshold(): Promise { return this.circuits.impure.getThreshold(); } - public isSigner(account: Uint8Array): boolean { + public isSigner(account: Uint8Array): Promise { return this.circuits.impure.isSigner(account); } - public _addSigner(signer: Uint8Array) { + public _addSigner(signer: Uint8Array): Promise<[]> { return this.circuits.impure._addSigner(signer); } - public _removeSigner(signer: Uint8Array) { + public _removeSigner(signer: Uint8Array): Promise<[]> { return this.circuits.impure._removeSigner(signer); } - public _changeThreshold(newThreshold: bigint) { + public _changeThreshold(newThreshold: bigint): Promise<[]> { return this.circuits.impure._changeThreshold(newThreshold); } - public _setThreshold(newThreshold: bigint) { + public _setThreshold(newThreshold: bigint): Promise<[]> { return this.circuits.impure._setThreshold(newThreshold); } } diff --git a/contracts/src/multisig/test/simulators/presets/ForwarderPrivateSimulator.ts b/contracts/src/multisig/test/simulators/presets/ForwarderPrivateSimulator.ts index 2b90a5e0..8c06eb45 100644 --- a/contracts/src/multisig/test/simulators/presets/ForwarderPrivateSimulator.ts +++ b/contracts/src/multisig/test/simulators/presets/ForwarderPrivateSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { Contract as ForwarderPrivate, @@ -28,17 +28,22 @@ const ForwarderPrivateSimulatorBase = createSimulator< contractArgs: (parentCommitment) => [parentCommitment], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => emptyWitnesses(), + artifactName: 'ForwarderPrivate', }); export class ForwarderPrivateSimulator extends ForwarderPrivateSimulatorBase { - constructor( + static async create( parentCommitment: Uint8Array, - options: BaseSimulatorOptions< + options: SimulatorOptions< EmptyPrivateState, ReturnType > = {}, - ) { - super([parentCommitment], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [parentCommitment], + options, + ) as Promise; } public static calculateParentCommitment( @@ -48,7 +53,7 @@ export class ForwarderPrivateSimulator extends ForwarderPrivateSimulatorBase { return pureCircuits.calculateParentCommitment(parentAddr, opSecret); } - public deposit(coin: ShieldedCoinInfo) { + public deposit(coin: ShieldedCoinInfo): Promise<[]> { return this.circuits.impure.deposit(coin); } @@ -57,11 +62,11 @@ export class ForwarderPrivateSimulator extends ForwarderPrivateSimulatorBase { parent: ZswapCoinPublicKey, opSecret: Uint8Array, value: bigint, - ): ShieldedSendResult { + ): Promise { return this.circuits.impure.drain(coin, parent, opSecret, value); } - public getParentCommitment(): Uint8Array { + public getParentCommitment(): Promise { return this.circuits.impure.getParentCommitment(); } } diff --git a/contracts/src/multisig/test/simulators/presets/ForwarderShieldedSimulator.ts b/contracts/src/multisig/test/simulators/presets/ForwarderShieldedSimulator.ts index aec81b82..e34b9717 100644 --- a/contracts/src/multisig/test/simulators/presets/ForwarderShieldedSimulator.ts +++ b/contracts/src/multisig/test/simulators/presets/ForwarderShieldedSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -27,24 +27,29 @@ const ForwarderShieldedSimulatorBase = createSimulator< contractArgs: (parent) => [parent], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => emptyWitnesses(), + artifactName: 'ForwarderShielded', }); export class ForwarderShieldedSimulator extends ForwarderShieldedSimulatorBase { - constructor( + static async create( parent: ZswapCoinPublicKey, - options: BaseSimulatorOptions< + options: SimulatorOptions< EmptyPrivateState, ReturnType > = {}, - ) { - super([parent], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [parent], + options, + ) as Promise; } - public deposit(coin: ShieldedCoinInfo) { + public deposit(coin: ShieldedCoinInfo): Promise<[]> { return this.circuits.impure.deposit(coin); } - public getParent(): Either { + public getParent(): Promise> { return this.circuits.impure.getParent(); } } diff --git a/contracts/src/multisig/test/simulators/presets/ForwarderUnshieldedSimulator.ts b/contracts/src/multisig/test/simulators/presets/ForwarderUnshieldedSimulator.ts index 78dc9adb..0119fe24 100644 --- a/contracts/src/multisig/test/simulators/presets/ForwarderUnshieldedSimulator.ts +++ b/contracts/src/multisig/test/simulators/presets/ForwarderUnshieldedSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -26,24 +26,29 @@ const ForwarderUnshieldedSimulatorBase = createSimulator< contractArgs: (parent) => [parent], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => emptyWitnesses(), + artifactName: 'ForwarderUnshielded', }); export class ForwarderUnshieldedSimulator extends ForwarderUnshieldedSimulatorBase { - constructor( + static async create( parent: UserAddress, - options: BaseSimulatorOptions< + options: SimulatorOptions< EmptyPrivateState, ReturnType > = {}, - ) { - super([parent], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [parent], + options, + ) as Promise; } - public deposit(color: Uint8Array, amount: bigint) { + public deposit(color: Uint8Array, amount: bigint): Promise<[]> { return this.circuits.impure.deposit(color, amount); } - public getParent(): Either { + public getParent(): Promise> { return this.circuits.impure.getParent(); } } diff --git a/contracts/src/security/test/Initializable.test.ts b/contracts/src/security/test/Initializable.test.ts index 5f96eea9..6ac860e4 100644 --- a/contracts/src/security/test/Initializable.test.ts +++ b/contracts/src/security/test/Initializable.test.ts @@ -4,62 +4,62 @@ import { InitializableSimulator } from './simulators/InitializableSimulator.js'; let initializable: InitializableSimulator; describe('Initializable', () => { - beforeEach(() => { - initializable = new InitializableSimulator(); + beforeEach(async () => { + initializable = await InitializableSimulator.create(); }); - it('should generate the initial ledger state deterministically', () => { - const initializable2 = new InitializableSimulator(); - expect(initializable.getPublicState()).toEqual( - initializable2.getPublicState(), + it('should generate the initial ledger state deterministically', async () => { + const initializable2 = await InitializableSimulator.create(); + expect(await initializable.getPublicState()).toEqual( + await initializable2.getPublicState(), ); }); describe('initialize', () => { - it('should not be initialized', () => { + it('should not be initialized', async () => { expect( - initializable.getPublicState().Initializable__isInitialized, + (await initializable.getPublicState()).Initializable__isInitialized, ).toEqual(false); }); - it('should initialize', () => { - initializable.initialize(); + it('should initialize', async () => { + await initializable.initialize(); expect( - initializable.getPublicState().Initializable__isInitialized, + (await initializable.getPublicState()).Initializable__isInitialized, ).toEqual(true); }); }); - it('should fail when re-initialized', () => { - expect(() => { - initializable.initialize(); - initializable.initialize(); - }).toThrow('Initializable: contract already initialized'); + it('should fail when re-initialized', async () => { + await initializable.initialize(); + await expect(initializable.initialize()).rejects.toThrow( + 'Initializable: contract already initialized', + ); }); describe('assertInitialized', () => { - it('should fail when not initialized', () => { - expect(() => { - initializable.assertInitialized(); - }).toThrow('Initializable: contract not initialized'); + it('should fail when not initialized', async () => { + await expect(initializable.assertInitialized()).rejects.toThrow( + 'Initializable: contract not initialized', + ); }); - it('should not fail when initialized', () => { - initializable.initialize(); - initializable.assertInitialized(); + it('should not fail when initialized', async () => { + await initializable.initialize(); + await initializable.assertInitialized(); }); }); describe('assertNotInitialized', () => { - it('should fail when initialized', () => { - initializable.initialize(); - expect(() => { - initializable.assertNotInitialized(); - }).toThrow('Initializable: contract already initialized'); + it('should fail when initialized', async () => { + await initializable.initialize(); + await expect(initializable.assertNotInitialized()).rejects.toThrow( + 'Initializable: contract already initialized', + ); }); - it('should not fail when not initialied', () => { - initializable.assertNotInitialized(); + it('should not fail when not initialied', async () => { + await initializable.assertNotInitialized(); }); }); }); diff --git a/contracts/src/security/test/Pausable.test.ts b/contracts/src/security/test/Pausable.test.ts index 6148d804..82927336 100644 --- a/contracts/src/security/test/Pausable.test.ts +++ b/contracts/src/security/test/Pausable.test.ts @@ -4,86 +4,82 @@ import { PausableSimulator } from './simulators/PausableSimulator.js'; let pausable: PausableSimulator; describe('Pausable', () => { - beforeEach(() => { - pausable = new PausableSimulator(); + beforeEach(async () => { + pausable = await PausableSimulator.create(); }); describe('when not paused', () => { - it('should not be paused in initial state', () => { - expect(pausable.isPaused()).toBe(false); + it('should not be paused in initial state', async () => { + expect(await pausable.isPaused()).toBe(false); }); - it('should throw when calling assertPaused', () => { - expect(() => { - pausable.assertPaused(); - }).toThrow('Pausable: not paused'); + it('should throw when calling assertPaused', async () => { + await expect(pausable.assertPaused()).rejects.toThrow( + 'Pausable: not paused', + ); }); - it('should not throw when calling assertNotPaused', () => { - pausable.assertNotPaused(); + it('should not throw when calling assertNotPaused', async () => { + await pausable.assertNotPaused(); }); - it('should pause from unpaused state', () => { - pausable.pause(); - expect(pausable.isPaused()).toBe(true); + it('should pause from unpaused state', async () => { + await pausable.pause(); + expect(await pausable.isPaused()).toBe(true); }); - it('should throw when unpausing in an unpaused state', () => { - expect(() => { - pausable.unpause(); - }).toThrow('Pausable: not paused'); + it('should throw when unpausing in an unpaused state', async () => { + await expect(pausable.unpause()).rejects.toThrow('Pausable: not paused'); }); }); describe('when paused', () => { - beforeEach(() => { - pausable.pause(); + beforeEach(async () => { + await pausable.pause(); }); - it('should not throw when calling assertPaused', () => { - pausable.assertPaused(); + it('should not throw when calling assertPaused', async () => { + await pausable.assertPaused(); }); - it('should throw when calling assertNotPaused', () => { - expect(() => { - pausable.assertNotPaused(); - }).toThrow('Pausable: paused'); + it('should throw when calling assertNotPaused', async () => { + await expect(pausable.assertNotPaused()).rejects.toThrow( + 'Pausable: paused', + ); }); - it('should unpause from paused state', () => { - pausable.unpause(); - expect(pausable.isPaused()).toBe(false); + it('should unpause from paused state', async () => { + await pausable.unpause(); + expect(await pausable.isPaused()).toBe(false); }); - it('should throw when pausing in an paused state', () => { - expect(() => { - pausable.pause(); - }).toThrow('Pausable: paused'); + it('should throw when pausing in an paused state', async () => { + await expect(pausable.pause()).rejects.toThrow('Pausable: paused'); }); }); describe('Multiple Operations', () => { - it('should handle pause → unpause → pause sequence', () => { - pausable.pause(); - expect(pausable.isPaused()).toBe(true); + it('should handle pause → unpause → pause sequence', async () => { + await pausable.pause(); + expect(await pausable.isPaused()).toBe(true); - pausable.unpause(); - expect(pausable.isPaused()).toBe(false); + await pausable.unpause(); + expect(await pausable.isPaused()).toBe(false); - pausable.pause(); - expect(pausable.isPaused()).toBe(true); + await pausable.pause(); + expect(await pausable.isPaused()).toBe(true); }); }); describe('simulator wiring', () => { - it('should expose the public ledger via getPublicState', () => { - const sim = new PausableSimulator(); + it('should expose the public ledger via getPublicState', async () => { + const sim = await PausableSimulator.create(); - expect(sim.getPublicState().Pausable__isPaused).toBe(false); + expect((await sim.getPublicState()).Pausable__isPaused).toBe(false); - sim.pause(); + await sim.pause(); - expect(sim.getPublicState().Pausable__isPaused).toBe(true); + expect((await sim.getPublicState()).Pausable__isPaused).toBe(true); }); }); }); diff --git a/contracts/src/security/test/simulators/InitializableSimulator.ts b/contracts/src/security/test/simulators/InitializableSimulator.ts index 19a2c705..087e66e3 100644 --- a/contracts/src/security/test/simulators/InitializableSimulator.ts +++ b/contracts/src/security/test/simulators/InitializableSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { ledger, @@ -29,41 +29,43 @@ const InitializableSimulatorBase = createSimulator< contractArgs: () => [], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => InitializableWitnesses(), + artifactName: 'MockInitializable', }); /** * Initializable Simulator */ export class InitializableSimulator extends InitializableSimulatorBase { - constructor( - options: BaseSimulatorOptions< + static async create( + options: SimulatorOptions< InitializablePrivateState, ReturnType > = {}, - ) { - super([], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create([], options) as Promise; } /** * @description Initializes the state. */ - public initialize() { - this.circuits.impure.initialize(); + public initialize(): Promise<[]> { + return this.circuits.impure.initialize(); } /** * @description Asserts that the contract has been initialized, throwing an error if not. * @throws Will throw "Initializable: contract not initialized" if the contract is not initialized. */ - public assertInitialized() { - this.circuits.impure.assertInitialized(); + public assertInitialized(): Promise<[]> { + return this.circuits.impure.assertInitialized(); } /** * @description Asserts that the contract has not been initialized, throwing an error if it has. * @throws Will throw "Initializable: contract already initialized" if the contract is already initialized. */ - public assertNotInitialized() { - this.circuits.impure.assertNotInitialized(); + public assertNotInitialized(): Promise<[]> { + return this.circuits.impure.assertNotInitialized(); } } diff --git a/contracts/src/security/test/simulators/PausableSimulator.ts b/contracts/src/security/test/simulators/PausableSimulator.ts index 90338353..5110ca07 100644 --- a/contracts/src/security/test/simulators/PausableSimulator.ts +++ b/contracts/src/security/test/simulators/PausableSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { ledger, @@ -29,54 +29,56 @@ const PausableSimulatorBase = createSimulator< contractArgs: () => [], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => PausableWitnesses(), + artifactName: 'MockPausable', }); /** * Pausable Simulator */ export class PausableSimulator extends PausableSimulatorBase { - constructor( - options: BaseSimulatorOptions< + static async create( + options: SimulatorOptions< PausablePrivateState, ReturnType > = {}, - ) { - super([], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create([], options) as Promise; } /** * @description Returns true if the contract is paused, and false otherwise. * @returns True if paused. */ - public isPaused(): boolean { + public isPaused(): Promise { return this.circuits.impure.isPaused(); } /** * @description Makes a circuit only callable when the contract is paused. */ - public assertPaused() { - this.circuits.impure.assertPaused(); + public assertPaused(): Promise<[]> { + return this.circuits.impure.assertPaused(); } /** * @description Makes a circuit only callable when the contract is not paused. */ - public assertNotPaused() { - this.circuits.impure.assertNotPaused(); + public assertNotPaused(): Promise<[]> { + return this.circuits.impure.assertNotPaused(); } /** * @description Triggers a stopped state. */ - public pause() { - this.circuits.impure.pause(); + public pause(): Promise<[]> { + return this.circuits.impure.pause(); } /** * @description Lifts the pause on the contract. */ - public unpause() { - this.circuits.impure.unpause(); + public unpause(): Promise<[]> { + return this.circuits.impure.unpause(); } } diff --git a/contracts/src/token/test/FungibleToken.test.ts b/contracts/src/token/test/FungibleToken.test.ts index 47f0e18b..19a2c121 100644 --- a/contracts/src/token/test/FungibleToken.test.ts +++ b/contracts/src/token/test/FungibleToken.test.ts @@ -91,29 +91,29 @@ const recipientTypes = [ describe('FungibleToken', () => { describe('before initialization', () => { - it('should initialize metadata', () => { - token = new FungibleTokenSimulator(NAME, SYMBOL, DECIMALS, INIT); - expect(token.name()).toEqual(NAME); - expect(token.symbol()).toEqual(SYMBOL); - expect(token.decimals()).toEqual(DECIMALS); + it('should initialize metadata', async () => { + token = await FungibleTokenSimulator.create(NAME, SYMBOL, DECIMALS, INIT); + expect(await token.name()).toEqual(NAME); + expect(await token.symbol()).toEqual(SYMBOL); + expect(await token.decimals()).toEqual(DECIMALS); }); - it('should initialize empty metadata', () => { - token = new FungibleTokenSimulator( + it('should initialize empty metadata', async () => { + token = await FungibleTokenSimulator.create( EMPTY_STRING, EMPTY_STRING, NO_DECIMALS, INIT, ); - expect(token.name()).toEqual(EMPTY_STRING); - expect(token.symbol()).toEqual(EMPTY_STRING); - expect(token.decimals()).toEqual(NO_DECIMALS); + expect(await token.name()).toEqual(EMPTY_STRING); + expect(await token.symbol()).toEqual(EMPTY_STRING); + expect(await token.decimals()).toEqual(NO_DECIMALS); }); }); describe('when not initialized correctly', () => { - beforeEach(() => { - token = new FungibleTokenSimulator( + beforeEach(async () => { + token = await FungibleTokenSimulator.create( EMPTY_STRING, EMPTY_STRING, NO_DECIMALS, @@ -146,43 +146,45 @@ describe('FungibleToken', () => { ['_burn', [OWNER.either, AMOUNT]], ]; - it.each(circuitsToFail)('%s should fail', (circuitName, args) => { - expect(() => { - (token[circuitName] as (...args: unknown[]) => unknown)(...args); - }).toThrow('FungibleToken: contract not initialized'); + it.each(circuitsToFail)('%s should fail', async (circuitName, args) => { + await expect( + (token[circuitName] as (...args: unknown[]) => Promise)( + ...args, + ), + ).rejects.toThrow('FungibleToken: contract not initialized'); }); }); describe('when initialized correctly', () => { - beforeEach(() => { - token = new FungibleTokenSimulator(NAME, SYMBOL, DECIMALS, INIT); + beforeEach(async () => { + token = await FungibleTokenSimulator.create(NAME, SYMBOL, DECIMALS, INIT); }); describe('totalSupply', () => { - it('returns 0 when there is no supply', () => { - expect(token.totalSupply()).toEqual(0n); + it('returns 0 when there is no supply', async () => { + expect(await token.totalSupply()).toEqual(0n); }); - it('returns the amount of existing tokens when there is a supply', () => { - token._mint(OWNER.either, AMOUNT); - expect(token.totalSupply()).toEqual(AMOUNT); + it('returns the amount of existing tokens when there is a supply', async () => { + await token._mint(OWNER.either, AMOUNT); + expect(await token.totalSupply()).toEqual(AMOUNT); }); }); describe('balanceOf', () => { describe.each(ownerTypes)('when the owner is a %s', (_, owner) => { - it('should return zero when requested account has no balance', () => { - expect(token.balanceOf(owner)).toEqual(0n); + it('should return zero when requested account has no balance', async () => { + expect(await token.balanceOf(owner)).toEqual(0n); }); - it('should return balance when requested account has tokens', () => { - token._unsafeMint(owner, AMOUNT); - expect(token.balanceOf(owner)).toEqual(AMOUNT); + it('should return balance when requested account has tokens', async () => { + await token._unsafeMint(owner, AMOUNT); + expect(await token.balanceOf(owner)).toEqual(AMOUNT); }); }); - it('should return correct balance with non-canonical lookup (left)', () => { - token._mint(OWNER.either, AMOUNT); + it('should return correct balance with non-canonical lookup (left)', async () => { + await token._mint(OWNER.either, AMOUNT); const nonCanonical = { is_left: true, @@ -190,11 +192,11 @@ describe('FungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - expect(token.balanceOf(nonCanonical)).toEqual(AMOUNT); + expect(await token.balanceOf(nonCanonical)).toEqual(AMOUNT); }); - it('should return correct balance with non-canonical lookup (right)', () => { - token._unsafeMint(OWNER_CONTRACT, AMOUNT); + it('should return correct balance with non-canonical lookup (right)', async () => { + await token._unsafeMint(OWNER_CONTRACT, AMOUNT); const nonCanonical = { is_left: false, @@ -202,13 +204,13 @@ describe('FungibleToken', () => { right: OWNER_CONTRACT.right, }; - expect(token.balanceOf(nonCanonical)).toEqual(AMOUNT); + expect(await token.balanceOf(nonCanonical)).toEqual(AMOUNT); }); }); describe('allowance', () => { - it('should return correct allowance with non-canonical owner lookup (left)', () => { - token._approve(OWNER.either, SPENDER.either, AMOUNT); + it('should return correct allowance with non-canonical owner lookup (left)', async () => { + await token._approve(OWNER.either, SPENDER.either, AMOUNT); const nonCanonicalOwner = { is_left: true, @@ -216,13 +218,13 @@ describe('FungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - expect(token.allowance(nonCanonicalOwner, SPENDER.either)).toEqual( - AMOUNT, - ); + expect( + await token.allowance(nonCanonicalOwner, SPENDER.either), + ).toEqual(AMOUNT); }); - it('should return correct allowance with non-canonical spender lookup (left)', () => { - token._approve(OWNER.either, SPENDER.either, AMOUNT); + it('should return correct allowance with non-canonical spender lookup (left)', async () => { + await token._approve(OWNER.either, SPENDER.either, AMOUNT); const nonCanonicalSpender = { is_left: true, @@ -230,13 +232,13 @@ describe('FungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - expect(token.allowance(OWNER.either, nonCanonicalSpender)).toEqual( - AMOUNT, - ); + expect( + await token.allowance(OWNER.either, nonCanonicalSpender), + ).toEqual(AMOUNT); }); - it('should return correct allowance with non-canonical owner lookup (right)', () => { - token._approve(OWNER_CONTRACT, SPENDER.either, AMOUNT); + it('should return correct allowance with non-canonical owner lookup (right)', async () => { + await token._approve(OWNER_CONTRACT, SPENDER.either, AMOUNT); const nonCanonicalOwner = { is_left: false, @@ -244,13 +246,13 @@ describe('FungibleToken', () => { right: OWNER_CONTRACT.right, }; - expect(token.allowance(nonCanonicalOwner, SPENDER.either)).toEqual( - AMOUNT, - ); + expect( + await token.allowance(nonCanonicalOwner, SPENDER.either), + ).toEqual(AMOUNT); }); - it('should return correct allowance with non-canonical spender lookup (right)', () => { - token._approve(OWNER.either, RECIPIENT_CONTRACT, AMOUNT); + it('should return correct allowance with non-canonical spender lookup (right)', async () => { + await token._approve(OWNER.either, RECIPIENT_CONTRACT, AMOUNT); const nonCanonicalSpender = { is_left: false, @@ -258,96 +260,96 @@ describe('FungibleToken', () => { right: RECIPIENT_CONTRACT.right, }; - expect(token.allowance(OWNER.either, nonCanonicalSpender)).toEqual( - AMOUNT, - ); + expect( + await token.allowance(OWNER.either, nonCanonicalSpender), + ).toEqual(AMOUNT); }); }); describe('transfer', () => { - beforeEach(() => { - token._mint(OWNER.either, AMOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT); - expect(token.balanceOf(RECIPIENT.either)).toEqual(0n); + beforeEach(async () => { + await token._mint(OWNER.either, AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(0n); }); - afterEach(() => { - expect(token.totalSupply()).toEqual(AMOUNT); + afterEach(async () => { + expect(await token.totalSupply()).toEqual(AMOUNT); }); - it('should transfer partial', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should transfer partial', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); const partialAmt = AMOUNT - 1n; - const txSuccess = token.transfer(RECIPIENT.either, partialAmt); + const txSuccess = await token.transfer(RECIPIENT.either, partialAmt); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(RECIPIENT.either)).toEqual(partialAmt); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(partialAmt); }); - it('should transfer full', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should transfer full', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - const txSuccess = token.transfer(RECIPIENT.either, AMOUNT); + const txSuccess = await token.transfer(RECIPIENT.either, AMOUNT); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); }); - it('should fail with insufficient balance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should fail with insufficient balance', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.transfer(RECIPIENT.either, AMOUNT + 1n); - }).toThrow('FungibleToken: insufficient balance'); + await expect( + token.transfer(RECIPIENT.either, AMOUNT + 1n), + ).rejects.toThrow('FungibleToken: insufficient balance'); }); - it('should fail with transfer from zero identity', () => { + it('should fail with transfer from zero identity', async () => { // Inject a key that produces zero accountId — infeasible in practice, // but we can test the zero check by using _unsafeUncheckedTransfer directly - expect(() => { + await expect( token._unsafeUncheckedTransfer( ZERO_ACCOUNT, RECIPIENT.either, AMOUNT, - ); - }).toThrow('FungibleToken: invalid sender'); + ), + ).rejects.toThrow('FungibleToken: invalid sender'); }); - it('should fail with transfer to zero', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should fail with transfer to zero', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.transfer(ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + await expect(token.transfer(ZERO_ACCOUNT, AMOUNT)).rejects.toThrow( + 'FungibleToken: invalid receiver', + ); }); - it('should allow transfer of 0 tokens', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should allow transfer of 0 tokens', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - const txSuccess = token.transfer(RECIPIENT.either, 0n); + const txSuccess = await token.transfer(RECIPIENT.either, 0n); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT); - expect(token.balanceOf(RECIPIENT.either)).toEqual(0n); + expect(await token.balanceOf(OWNER.either)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(0n); }); - it('should handle transfer with empty _balances', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should handle transfer with empty _balances', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.transfer(RECIPIENT.either, 1n); - }).toThrow('FungibleToken: insufficient balance'); + await expect(token.transfer(RECIPIENT.either, 1n)).rejects.toThrow( + 'FungibleToken: insufficient balance', + ); }); - it('should fail when transferring to a contract', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should fail when transferring to a contract', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.transfer(OWNER_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: unsafe transfer'); + await expect(token.transfer(OWNER_CONTRACT, AMOUNT)).rejects.toThrow( + 'FungibleToken: unsafe transfer', + ); }); }); @@ -355,437 +357,453 @@ describe('FungibleToken', () => { describe.each( recipientTypes, )('when the recipient is a %s', (_, recipient) => { - beforeEach(() => { - token._mint(OWNER.either, AMOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT); - expect(token.balanceOf(recipient)).toEqual(0n); + beforeEach(async () => { + await token._mint(OWNER.either, AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(AMOUNT); + expect(await token.balanceOf(recipient)).toEqual(0n); }); - afterEach(() => { - expect(token.totalSupply()).toEqual(AMOUNT); + afterEach(async () => { + expect(await token.totalSupply()).toEqual(AMOUNT); }); - it('should transfer partial', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should transfer partial', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); const partialAmt = AMOUNT - 1n; - const txSuccess = token._unsafeTransfer(recipient, partialAmt); + const txSuccess = await token._unsafeTransfer(recipient, partialAmt); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(recipient)).toEqual(partialAmt); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(recipient)).toEqual(partialAmt); }); - it('should transfer full', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should transfer full', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - const txSuccess = token._unsafeTransfer(recipient, AMOUNT); + const txSuccess = await token._unsafeTransfer(recipient, AMOUNT); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(recipient)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(recipient)).toEqual(AMOUNT); }); - it('should fail with insufficient balance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should fail with insufficient balance', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token._unsafeTransfer(recipient, AMOUNT + 1n); - }).toThrow('FungibleToken: insufficient balance'); + await expect( + token._unsafeTransfer(recipient, AMOUNT + 1n), + ).rejects.toThrow('FungibleToken: insufficient balance'); }); - it('should allow transfer of 0 tokens', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should allow transfer of 0 tokens', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - const txSuccess = token._unsafeTransfer(recipient, 0n); + const txSuccess = await token._unsafeTransfer(recipient, 0n); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT); - expect(token.balanceOf(recipient)).toEqual(0n); + expect(await token.balanceOf(OWNER.either)).toEqual(AMOUNT); + expect(await token.balanceOf(recipient)).toEqual(0n); }); - it('should handle transfer with empty _balances', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should handle transfer with empty _balances', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token._unsafeTransfer(recipient, 1n); - }).toThrow('FungibleToken: insufficient balance'); + await expect(token._unsafeTransfer(recipient, 1n)).rejects.toThrow( + 'FungibleToken: insufficient balance', + ); }); }); - it('should fail with transfer to zero (accountId)', () => { - token._mint(OWNER.either, AMOUNT); - token.privateState.injectSecretKey(OWNER.secretKey); + it('should fail with transfer to zero (accountId)', async () => { + await token._mint(OWNER.either, AMOUNT); + await token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token._unsafeTransfer(ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + await expect( + token._unsafeTransfer(ZERO_ACCOUNT, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid receiver'); }); - it('should fail with transfer to zero (contract)', () => { - token._mint(OWNER.either, AMOUNT); - token.privateState.injectSecretKey(OWNER.secretKey); + it('should fail with transfer to zero (contract)', async () => { + await token._mint(OWNER.either, AMOUNT); + await token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token._unsafeTransfer(ZERO_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + await expect( + token._unsafeTransfer(ZERO_CONTRACT, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid receiver'); }); }); describe('approve', () => { - beforeEach(() => { - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); + beforeEach(async () => { + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); }); - it('should approve and update allowance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should approve and update allowance', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(AMOUNT); + await token.approve(SPENDER.either, AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( + AMOUNT, + ); }); - it('should approve and update allowance for multiple spenders', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should approve and update allowance for multiple spenders', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(AMOUNT); + await token.approve(SPENDER.either, AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( + AMOUNT, + ); - token.approve(OTHER.either, AMOUNT); - expect(token.allowance(OWNER.either, OTHER.either)).toEqual(AMOUNT); + await token.approve(OTHER.either, AMOUNT); + expect(await token.allowance(OWNER.either, OTHER.either)).toEqual( + AMOUNT, + ); - expect(token.allowance(OWNER.either, RECIPIENT.either)).toEqual(0n); + expect(await token.allowance(OWNER.either, RECIPIENT.either)).toEqual( + 0n, + ); }); - it('should fail when approve to zero', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should fail when approve to zero', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.approve(ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid spender'); + await expect(token.approve(ZERO_ACCOUNT, AMOUNT)).rejects.toThrow( + 'FungibleToken: invalid spender', + ); }); - it('should transfer exact allowance and fail subsequent transfer', () => { - token._mint(OWNER.either, AMOUNT); + it('should transfer exact allowance and fail subsequent transfer', async () => { + await token._mint(OWNER.either, AMOUNT); - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, AMOUNT); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, AMOUNT); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); - expect(() => { - token.transferFrom(OWNER.either, RECIPIENT.either, 1n); - }).toThrow('FungibleToken: insufficient allowance'); + await expect( + token.transferFrom(OWNER.either, RECIPIENT.either, 1n), + ).rejects.toThrow('FungibleToken: insufficient allowance'); }); - it('should allow approve of 0 tokens', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should allow approve of 0 tokens', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, 0n); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); + await token.approve(SPENDER.either, 0n); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); }); - it('should handle allowance with empty _allowances', () => { - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); + it('should handle allowance with empty _allowances', async () => { + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); }); }); describe('transferFrom', () => { - beforeEach(() => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, AMOUNT); - token._mint(OWNER.either, AMOUNT); + beforeEach(async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, AMOUNT); + await token._mint(OWNER.either, AMOUNT); }); - afterEach(() => { - expect(token.totalSupply()).toEqual(AMOUNT); + afterEach(async () => { + expect(await token.totalSupply()).toEqual(AMOUNT); }); - it('should transferFrom spender (partial)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should transferFrom spender (partial)', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); const partialAmt = AMOUNT - 1n; - const txSuccess = token.transferFrom( + const txSuccess = await token.transferFrom( OWNER.either, RECIPIENT.either, partialAmt, ); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(RECIPIENT.either)).toEqual(partialAmt); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(1n); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(partialAmt); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual(1n); }); - it('should transferFrom spender (full)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should transferFrom spender (full)', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - const txSuccess = token.transferFrom( + const txSuccess = await token.transferFrom( OWNER.either, RECIPIENT.either, AMOUNT, ); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); }); - it('should transferFrom and not consume infinite allowance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, MAX_UINT128); + it('should transferFrom and not consume infinite allowance', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, MAX_UINT128); - token.privateState.injectSecretKey(SPENDER.secretKey); - const txSuccess = token.transferFrom( + await token.privateState.injectSecretKey(SPENDER.secretKey); + const txSuccess = await token.transferFrom( OWNER.either, RECIPIENT.either, AMOUNT, ); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual( + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( MAX_UINT128, ); }); - it('should fail when transfer amount exceeds allowance', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should fail when transfer amount exceeds allowance', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT + 1n); - }).toThrow('FungibleToken: insufficient allowance'); + await expect( + token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT + 1n), + ).rejects.toThrow('FungibleToken: insufficient allowance'); }); - it('should fail when transfer amount exceeds balance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, AMOUNT + 1n); + it('should fail when transfer amount exceeds balance', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, AMOUNT + 1n); - token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT + 1n); - }).toThrow('FungibleToken: insufficient balance'); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await expect( + token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT + 1n), + ).rejects.toThrow('FungibleToken: insufficient balance'); }); - it('should fail when spender does not have allowance', () => { - token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + it('should fail when spender does not have allowance', async () => { + await token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => { - token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT); - }).toThrow('FungibleToken: insufficient allowance'); + await expect( + token.transferFrom(OWNER.either, RECIPIENT.either, AMOUNT), + ).rejects.toThrow('FungibleToken: insufficient allowance'); }); - it('should fail to transferFrom to the zero address', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should fail to transferFrom to the zero address', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + await expect( + token.transferFrom(OWNER.either, ZERO_ACCOUNT, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid receiver'); }); - it('should fail when transferring to a contract', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should fail when transferring to a contract', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, OWNER_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: unsafe transfer'); + await expect( + token.transferFrom(OWNER.either, OWNER_CONTRACT, AMOUNT), + ).rejects.toThrow('FungibleToken: unsafe transfer'); }); }); describe('_unsafeTransferFrom', () => { - beforeEach(() => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, AMOUNT); - token._mint(OWNER.either, AMOUNT); + beforeEach(async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, AMOUNT); + await token._mint(OWNER.either, AMOUNT); }); - afterEach(() => { - expect(token.totalSupply()).toEqual(AMOUNT); + afterEach(async () => { + expect(await token.totalSupply()).toEqual(AMOUNT); }); describe.each( recipientTypes, )('when the recipient is a %s', (_, recipient) => { - it('should transferFrom spender (partial)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should transferFrom spender (partial)', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); const partialAmt = AMOUNT - 1n; - const txSuccess = token._unsafeTransferFrom( + const txSuccess = await token._unsafeTransferFrom( OWNER.either, recipient, partialAmt, ); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(recipient)).toEqual(partialAmt); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(1n); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(recipient)).toEqual(partialAmt); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( + 1n, + ); }); - it('should transferFrom spender (full)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should transferFrom spender (full)', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - const txSuccess = token._unsafeTransferFrom( + const txSuccess = await token._unsafeTransferFrom( OWNER.either, recipient, AMOUNT, ); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(recipient)).toEqual(AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(recipient)).toEqual(AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( + 0n, + ); }); - it('should transferFrom and not consume infinite allowance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, MAX_UINT128); + it('should transferFrom and not consume infinite allowance', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, MAX_UINT128); - token.privateState.injectSecretKey(SPENDER.secretKey); - const txSuccess = token._unsafeTransferFrom( + await token.privateState.injectSecretKey(SPENDER.secretKey); + const txSuccess = await token._unsafeTransferFrom( OWNER.either, recipient, AMOUNT, ); expect(txSuccess).toBe(true); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(recipient)).toEqual(AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual( + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(recipient)).toEqual(AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( MAX_UINT128, ); }); - it('should fail when transfer amount exceeds allowance', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should fail when transfer amount exceeds allowance', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token._unsafeTransferFrom(OWNER.either, recipient, AMOUNT + 1n); - }).toThrow('FungibleToken: insufficient allowance'); + await expect( + token._unsafeTransferFrom(OWNER.either, recipient, AMOUNT + 1n), + ).rejects.toThrow('FungibleToken: insufficient allowance'); }); - it('should fail when transfer amount exceeds balance', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, AMOUNT + 1n); + it('should fail when transfer amount exceeds balance', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, AMOUNT + 1n); - token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token._unsafeTransferFrom(OWNER.either, recipient, AMOUNT + 1n); - }).toThrow('FungibleToken: insufficient balance'); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await expect( + token._unsafeTransferFrom(OWNER.either, recipient, AMOUNT + 1n), + ).rejects.toThrow('FungibleToken: insufficient balance'); }); - it('should fail when spender does not have allowance', () => { - token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + it('should fail when spender does not have allowance', async () => { + await token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => { - token._unsafeTransferFrom(OWNER.either, recipient, AMOUNT); - }).toThrow('FungibleToken: insufficient allowance'); + await expect( + token._unsafeTransferFrom(OWNER.either, recipient, AMOUNT), + ).rejects.toThrow('FungibleToken: insufficient allowance'); }); }); - it('should fail to transfer to the zero address (accountId)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should fail to transfer to the zero address (accountId)', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token._unsafeTransferFrom(OWNER.either, ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + await expect( + token._unsafeTransferFrom(OWNER.either, ZERO_ACCOUNT, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid receiver'); }); - it('should fail to transfer to the zero address (contract)', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); + it('should fail to transfer to the zero address (contract)', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token._unsafeTransferFrom(OWNER.either, ZERO_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + await expect( + token._unsafeTransferFrom(OWNER.either, ZERO_CONTRACT, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid receiver'); }); }); describe('_transfer', () => { - beforeEach(() => { - token._mint(OWNER.either, AMOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, AMOUNT); }); - afterEach(() => { - expect(token.totalSupply()).toEqual(AMOUNT); + afterEach(async () => { + expect(await token.totalSupply()).toEqual(AMOUNT); }); - it('should update balances (partial)', () => { + it('should update balances (partial)', async () => { const partialAmt = AMOUNT - 1n; - token._transfer(OWNER.either, RECIPIENT.either, partialAmt); + await token._transfer(OWNER.either, RECIPIENT.either, partialAmt); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(RECIPIENT.either)).toEqual(partialAmt); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(partialAmt); }); - it('should fail when transferring to a contract', () => { - expect(() => { - token._transfer(OWNER.either, OWNER_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: unsafe transfer'); + it('should fail when transferring to a contract', async () => { + await expect( + token._transfer(OWNER.either, OWNER_CONTRACT, AMOUNT), + ).rejects.toThrow('FungibleToken: unsafe transfer'); }); }); describe('_unsafeUncheckedTransfer', () => { - beforeEach(() => { - token._mint(OWNER.either, AMOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, AMOUNT); }); - afterEach(() => { - expect(token.totalSupply()).toEqual(AMOUNT); + afterEach(async () => { + expect(await token.totalSupply()).toEqual(AMOUNT); }); describe.each( recipientTypes, )('when the recipient is a %s', (_, recipient) => { - it('should update balances (partial)', () => { + it('should update balances (partial)', async () => { const partialAmt = AMOUNT - 1n; - token._unsafeUncheckedTransfer(OWNER.either, recipient, partialAmt); + await token._unsafeUncheckedTransfer( + OWNER.either, + recipient, + partialAmt, + ); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(recipient)).toEqual(partialAmt); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(recipient)).toEqual(partialAmt); }); - it('should update balances (full)', () => { - token._unsafeUncheckedTransfer(OWNER.either, recipient, AMOUNT); + it('should update balances (full)', async () => { + await token._unsafeUncheckedTransfer(OWNER.either, recipient, AMOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(recipient)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(recipient)).toEqual(AMOUNT); }); - it('should fail when transfer amount exceeds balance', () => { - expect(() => { + it('should fail when transfer amount exceeds balance', async () => { + await expect( token._unsafeUncheckedTransfer( OWNER.either, recipient, AMOUNT + 1n, - ); - }).toThrow('FungibleToken: insufficient balance'); + ), + ).rejects.toThrow('FungibleToken: insufficient balance'); }); - it('should fail when transfer from zero', () => { - expect(() => { - token._unsafeUncheckedTransfer(ZERO_CONTRACT, recipient, AMOUNT); - }).toThrow('FungibleToken: invalid sender'); + it('should fail when transfer from zero', async () => { + await expect( + token._unsafeUncheckedTransfer(ZERO_CONTRACT, recipient, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid sender'); }); }); - it('should fail when transfer to zero (accountId)', () => { - expect(() => { - token._unsafeUncheckedTransfer(OWNER.either, ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + it('should fail when transfer to zero (accountId)', async () => { + await expect( + token._unsafeUncheckedTransfer(OWNER.either, ZERO_ACCOUNT, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid receiver'); }); - it('should fail when transfer to zero (contract)', () => { - expect(() => { - token._unsafeUncheckedTransfer(OWNER.either, ZERO_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + it('should fail when transfer to zero (contract)', async () => { + await expect( + token._unsafeUncheckedTransfer(OWNER.either, ZERO_CONTRACT, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid receiver'); }); - it('should canonicalize recipient (zero out inactive right side)', () => { + it('should canonicalize recipient (zero out inactive right side)', async () => { // Check init amt for recipient is zero - expect(token.balanceOf(RECIPIENT.either)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(0n); const nonCanonical = { is_left: true, @@ -793,75 +811,87 @@ describe('FungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - token._unsafeUncheckedTransfer(OWNER.either, nonCanonical, AMOUNT); - expect(token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); + await token._unsafeUncheckedTransfer( + OWNER.either, + nonCanonical, + AMOUNT, + ); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); }); - it('should canonicalize recipient contract address (zero out inactive left side)', () => { + it('should canonicalize recipient contract address (zero out inactive left side)', async () => { const nonCanonical = { is_left: false, left: new Uint8Array(32).fill(1), right: RECIPIENT_CONTRACT.right, }; - token._unsafeUncheckedTransfer(OWNER.either, nonCanonical, AMOUNT); - expect(token.balanceOf(RECIPIENT_CONTRACT)).toEqual(AMOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(0n); + await token._unsafeUncheckedTransfer( + OWNER.either, + nonCanonical, + AMOUNT, + ); + expect(await token.balanceOf(RECIPIENT_CONTRACT)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); }); - it('should canonicalize fromAddress (zero out inactive right side)', () => { + it('should canonicalize fromAddress (zero out inactive right side)', async () => { const nonCanonical = { is_left: true, left: OWNER.accountId, right: utils.encodeToAddress('JUNK_DATA'), }; - token._unsafeUncheckedTransfer(nonCanonical, RECIPIENT.either, AMOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); + await token._unsafeUncheckedTransfer( + nonCanonical, + RECIPIENT.either, + AMOUNT, + ); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); }); }); describe('_mint', () => { - it('should mint and update supply', () => { - expect(token.totalSupply()).toEqual(0n); + it('should mint and update supply', async () => { + expect(await token.totalSupply()).toEqual(0n); - token._mint(RECIPIENT.either, AMOUNT); - expect(token.totalSupply()).toEqual(AMOUNT); - expect(token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); + await token._mint(RECIPIENT.either, AMOUNT); + expect(await token.totalSupply()).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT); }); - it('should catch mint overflow', () => { - token._mint(RECIPIENT.either, MAX_UINT128); + it('should catch mint overflow', async () => { + await token._mint(RECIPIENT.either, MAX_UINT128); - expect(() => { - token._mint(RECIPIENT.either, 1n); - }).toThrow('FungibleToken: arithmetic overflow'); + await expect(token._mint(RECIPIENT.either, 1n)).rejects.toThrow( + 'FungibleToken: arithmetic overflow', + ); }); - it('should not mint to zero (accountId)', () => { - expect(() => { - token._mint(ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + it('should not mint to zero (accountId)', async () => { + await expect(token._mint(ZERO_ACCOUNT, AMOUNT)).rejects.toThrow( + 'FungibleToken: invalid receiver', + ); }); - it('should not mint to zero (contract)', () => { - expect(() => { - // caught by unsafe transfer guard first - token._mint(ZERO_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: unsafe transfer'); + it('should not mint to zero (contract)', async () => { + // caught by unsafe transfer guard first + await expect(token._mint(ZERO_CONTRACT, AMOUNT)).rejects.toThrow( + 'FungibleToken: unsafe transfer', + ); }); - it('should allow mint of 0 tokens', () => { - token._mint(OWNER.either, 0n); - expect(token.totalSupply()).toEqual(0n); - expect(token.balanceOf(OWNER.either)).toEqual(0n); + it('should allow mint of 0 tokens', async () => { + await token._mint(OWNER.either, 0n); + expect(await token.totalSupply()).toEqual(0n); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); }); - it('should fail when minting to a contract', () => { - expect(() => { - token._mint(OWNER_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: unsafe transfer'); + it('should fail when minting to a contract', async () => { + await expect(token._mint(OWNER_CONTRACT, AMOUNT)).rejects.toThrow( + 'FungibleToken: unsafe transfer', + ); }); }); @@ -869,229 +899,247 @@ describe('FungibleToken', () => { describe.each( recipientTypes, )('when the recipient is a %s', (_, recipient) => { - it('should mint and update supply', () => { - expect(token.totalSupply()).toEqual(0n); + it('should mint and update supply', async () => { + expect(await token.totalSupply()).toEqual(0n); - token._unsafeMint(recipient, AMOUNT); - expect(token.totalSupply()).toEqual(AMOUNT); - expect(token.balanceOf(recipient)).toEqual(AMOUNT); + await token._unsafeMint(recipient, AMOUNT); + expect(await token.totalSupply()).toEqual(AMOUNT); + expect(await token.balanceOf(recipient)).toEqual(AMOUNT); }); - it('should catch mint overflow', () => { - token._unsafeMint(recipient, MAX_UINT128); + it('should catch mint overflow', async () => { + await token._unsafeMint(recipient, MAX_UINT128); - expect(() => { - token._unsafeMint(recipient, 1n); - }).toThrow('FungibleToken: arithmetic overflow'); + await expect(token._unsafeMint(recipient, 1n)).rejects.toThrow( + 'FungibleToken: arithmetic overflow', + ); }); - it('should allow mint of 0 tokens', () => { - token._unsafeMint(recipient, 0n); - expect(token.totalSupply()).toEqual(0n); - expect(token.balanceOf(recipient)).toEqual(0n); + it('should allow mint of 0 tokens', async () => { + await token._unsafeMint(recipient, 0n); + expect(await token.totalSupply()).toEqual(0n); + expect(await token.balanceOf(recipient)).toEqual(0n); }); }); - it('should not mint to zero (accountId)', () => { - expect(() => { - token._unsafeMint(ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + it('should not mint to zero (accountId)', async () => { + await expect(token._unsafeMint(ZERO_ACCOUNT, AMOUNT)).rejects.toThrow( + 'FungibleToken: invalid receiver', + ); }); - it('should not mint to zero (contract)', () => { - expect(() => { - token._unsafeMint(ZERO_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: invalid receiver'); + it('should not mint to zero (contract)', async () => { + await expect(token._unsafeMint(ZERO_CONTRACT, AMOUNT)).rejects.toThrow( + 'FungibleToken: invalid receiver', + ); }); - it('should canonicalize sender (zero out inactive right side)', () => { + it('should canonicalize sender (zero out inactive right side)', async () => { const nonCanonical = { is_left: true, left: OWNER.accountId, right: utils.encodeToAddress('JUNK_DATA'), }; - token._unsafeMint(nonCanonical, AMOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT); + await token._unsafeMint(nonCanonical, AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(AMOUNT); }); }); describe('_burn', () => { - beforeEach(() => { - token._mint(OWNER.either, AMOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, AMOUNT); }); - it('should burn tokens', () => { - token._burn(OWNER.either, 1n); + it('should burn tokens', async () => { + await token._burn(OWNER.either, 1n); const afterBurn = AMOUNT - 1n; - expect(token.balanceOf(OWNER.either)).toEqual(afterBurn); - expect(token.totalSupply()).toEqual(afterBurn); + expect(await token.balanceOf(OWNER.either)).toEqual(afterBurn); + expect(await token.totalSupply()).toEqual(afterBurn); }); - it('should throw when burning from zero (accountId)', () => { - expect(() => { - token._burn(ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid sender'); + it('should throw when burning from zero (accountId)', async () => { + await expect(token._burn(ZERO_ACCOUNT, AMOUNT)).rejects.toThrow( + 'FungibleToken: invalid sender', + ); }); - it('should throw when burning from zero (contract)', () => { - expect(() => { - token._burn(ZERO_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: invalid sender'); + it('should throw when burning from zero (contract)', async () => { + await expect(token._burn(ZERO_CONTRACT, AMOUNT)).rejects.toThrow( + 'FungibleToken: invalid sender', + ); }); - it('should throw when burn amount is greater than balance', () => { - expect(() => { - token._burn(OWNER.either, AMOUNT + 1n); - }).toThrow('FungibleToken: insufficient balance'); + it('should throw when burn amount is greater than balance', async () => { + await expect(token._burn(OWNER.either, AMOUNT + 1n)).rejects.toThrow( + 'FungibleToken: insufficient balance', + ); }); - it('should allow burn of 0 tokens', () => { - token._burn(OWNER.either, 0n); - expect(token.totalSupply()).toEqual(AMOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT); + it('should allow burn of 0 tokens', async () => { + await token._burn(OWNER.either, 0n); + expect(await token.totalSupply()).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(AMOUNT); }); - it('should burn with non-canonical account (left)', () => { + it('should burn with non-canonical account (left)', async () => { const nonCanonical = { is_left: true, left: OWNER.accountId, right: utils.encodeToAddress('JUNK_DATA'), }; - token._burn(nonCanonical, 1n); - expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT - 1n); - expect(token.totalSupply()).toEqual(AMOUNT - 1n); + await token._burn(nonCanonical, 1n); + expect(await token.balanceOf(OWNER.either)).toEqual(AMOUNT - 1n); + expect(await token.totalSupply()).toEqual(AMOUNT - 1n); }); }); describe('_approve', () => { - beforeEach(() => { - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); + beforeEach(async () => { + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); }); - it('should approve and update allowance', () => { - token._approve(OWNER.either, SPENDER.either, AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(AMOUNT); + it('should approve and update allowance', async () => { + await token._approve(OWNER.either, SPENDER.either, AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( + AMOUNT, + ); }); - it('should approve and update allowance for multiple spenders', () => { - token._approve(OWNER.either, SPENDER.either, AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(AMOUNT); + it('should approve and update allowance for multiple spenders', async () => { + await token._approve(OWNER.either, SPENDER.either, AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( + AMOUNT, + ); - token._approve(OWNER.either, OTHER.either, AMOUNT); - expect(token.allowance(OWNER.either, OTHER.either)).toEqual(AMOUNT); + await token._approve(OWNER.either, OTHER.either, AMOUNT); + expect(await token.allowance(OWNER.either, OTHER.either)).toEqual( + AMOUNT, + ); - expect(token.allowance(OWNER.either, RECIPIENT.either)).toEqual(0n); + expect(await token.allowance(OWNER.either, RECIPIENT.either)).toEqual( + 0n, + ); }); - it('should fail when approve from zero (accountId)', () => { - expect(() => { - token._approve(ZERO_ACCOUNT, SPENDER.either, AMOUNT); - }).toThrow('FungibleToken: invalid owner'); + it('should fail when approve from zero (accountId)', async () => { + await expect( + token._approve(ZERO_ACCOUNT, SPENDER.either, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid owner'); }); - it('should fail when approve from zero (contract)', () => { - expect(() => { - token._approve(ZERO_CONTRACT, SPENDER.either, AMOUNT); - }).toThrow('FungibleToken: invalid owner'); + it('should fail when approve from zero (contract)', async () => { + await expect( + token._approve(ZERO_CONTRACT, SPENDER.either, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid owner'); }); - it('should fail when approve to zero (accountId)', () => { - expect(() => { - token._approve(OWNER.either, ZERO_ACCOUNT, AMOUNT); - }).toThrow('FungibleToken: invalid spender'); + it('should fail when approve to zero (accountId)', async () => { + await expect( + token._approve(OWNER.either, ZERO_ACCOUNT, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid spender'); }); - it('should fail when approve to zero (contract)', () => { - expect(() => { - token._approve(OWNER.either, ZERO_CONTRACT, AMOUNT); - }).toThrow('FungibleToken: invalid spender'); + it('should fail when approve to zero (contract)', async () => { + await expect( + token._approve(OWNER.either, ZERO_CONTRACT, AMOUNT), + ).rejects.toThrow('FungibleToken: invalid spender'); }); - it('should allow approve of 0 tokens', () => { - token._approve(OWNER.either, SPENDER.either, 0n); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); + it('should allow approve of 0 tokens', async () => { + await token._approve(OWNER.either, SPENDER.either, 0n); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); }); - it('should canonicalize owner in allowance (zero out inactive right side)', () => { + it('should canonicalize owner in allowance (zero out inactive right side)', async () => { const nonCanonicalOwner = { is_left: true, left: OWNER.accountId, right: utils.encodeToAddress('JUNK_DATA'), }; - token._approve(nonCanonicalOwner, SPENDER.either, AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(AMOUNT); + await token._approve(nonCanonicalOwner, SPENDER.either, AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( + AMOUNT, + ); }); - it('should canonicalize spender in allowance (zero out inactive right side)', () => { + it('should canonicalize spender in allowance (zero out inactive right side)', async () => { const nonCanonicalSpender = { is_left: true, left: SPENDER.accountId, right: utils.encodeToAddress('JUNK_DATA'), }; - token._approve(OWNER.either, nonCanonicalSpender, AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(AMOUNT); + await token._approve(OWNER.either, nonCanonicalSpender, AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( + AMOUNT, + ); }); - it('should canonicalize contract address owner (zero out inactive left side)', () => { + it('should canonicalize contract address owner (zero out inactive left side)', async () => { const nonCanonicalOwner = { is_left: false, left: new Uint8Array(32).fill(1), right: OWNER_CONTRACT.right, }; - token._approve(nonCanonicalOwner, SPENDER.either, AMOUNT); - expect(token.allowance(OWNER_CONTRACT, SPENDER.either)).toEqual(AMOUNT); + await token._approve(nonCanonicalOwner, SPENDER.either, AMOUNT); + expect(await token.allowance(OWNER_CONTRACT, SPENDER.either)).toEqual( + AMOUNT, + ); }); }); describe('_spendAllowance', () => { - beforeEach(() => { - token._mint(OWNER.either, AMOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, AMOUNT); }); - it('should update allowance when not unlimited', () => { - token._approve(OWNER.either, SPENDER.either, MAX_UINT128 - 1n); - token._spendAllowance(OWNER.either, SPENDER.either, AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual( + it('should update allowance when not unlimited', async () => { + await token._approve(OWNER.either, SPENDER.either, MAX_UINT128 - 1n); + await token._spendAllowance(OWNER.either, SPENDER.either, AMOUNT); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( MAX_UINT128 - 1n - AMOUNT, ); }); - it('should not update allowance when unlimited', () => { - token._approve(OWNER.either, SPENDER.either, MAX_UINT128); - token._spendAllowance(OWNER.either, SPENDER.either, MAX_UINT128 - 1n); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual( + it('should not update allowance when unlimited', async () => { + await token._approve(OWNER.either, SPENDER.either, MAX_UINT128); + await token._spendAllowance( + OWNER.either, + SPENDER.either, + MAX_UINT128 - 1n, + ); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual( MAX_UINT128, ); }); - it('should fail when owner allowance is not initialized', () => { - expect(() => { - token._spendAllowance(OTHER.either, SPENDER.either, AMOUNT); - }).toThrow('FungibleToken: insufficient allowance'); + it('should fail when owner allowance is not initialized', async () => { + await expect( + token._spendAllowance(OTHER.either, SPENDER.either, AMOUNT), + ).rejects.toThrow('FungibleToken: insufficient allowance'); }); - it('should fail when spender is not initialized', () => { - token._approve(OWNER.either, SPENDER.either, AMOUNT); - expect(() => { - token._spendAllowance(OWNER.either, OTHER.either, AMOUNT); - }).toThrow('FungibleToken: insufficient allowance'); + it('should fail when spender is not initialized', async () => { + await token._approve(OWNER.either, SPENDER.either, AMOUNT); + await expect( + token._spendAllowance(OWNER.either, OTHER.either, AMOUNT), + ).rejects.toThrow('FungibleToken: insufficient allowance'); }); - it('should fail when spender has insufficient allowance', () => { - token._approve(OWNER.either, SPENDER.either, AMOUNT); - expect(() => { - token._spendAllowance(OWNER.either, SPENDER.either, AMOUNT + 1n); - }).toThrow('FungibleToken: insufficient allowance'); + it('should fail when spender has insufficient allowance', async () => { + await token._approve(OWNER.either, SPENDER.either, AMOUNT); + await expect( + token._spendAllowance(OWNER.either, SPENDER.either, AMOUNT + 1n), + ).rejects.toThrow('FungibleToken: insufficient allowance'); }); - it('should canonicalize when spending allowance', () => { - token._approve(OWNER.either, SPENDER.either, AMOUNT); + it('should canonicalize when spending allowance', async () => { + await token._approve(OWNER.either, SPENDER.either, AMOUNT); const nonCanonicalOwner = { is_left: true, @@ -1104,52 +1152,74 @@ describe('FungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - token._spendAllowance(nonCanonicalOwner, nonCanonicalSpender, AMOUNT); - expect(token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); + await token._spendAllowance( + nonCanonicalOwner, + nonCanonicalSpender, + AMOUNT, + ); + expect(await token.allowance(OWNER.either, SPENDER.either)).toEqual(0n); }); }); describe('Multiple Operations', () => { - it('should handle mint → transfer → burn sequence', () => { - token._mint(OWNER.either, AMOUNT); - expect(token.totalSupply()).toEqual(AMOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT); + it('should handle mint → transfer → burn sequence', async () => { + await token._mint(OWNER.either, AMOUNT); + expect(await token.totalSupply()).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(AMOUNT); - token.privateState.injectSecretKey(OWNER.secretKey); - token.transfer(RECIPIENT.either, AMOUNT - 1n); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT - 1n); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.transfer(RECIPIENT.either, AMOUNT - 1n); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(AMOUNT - 1n); - token._burn(OWNER.either, 1n); - expect(token.totalSupply()).toEqual(AMOUNT - 1n); - expect(token.balanceOf(OWNER.either)).toEqual(0n); + await token._burn(OWNER.either, 1n); + expect(await token.totalSupply()).toEqual(AMOUNT - 1n); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); }); }); }); describe('simulator wiring', () => { - it('should expose an empty public ledger via getPublicState', () => { - const sim = new FungibleTokenSimulator(NAME, SYMBOL, DECIMALS, INIT); + it('should expose an empty public ledger via getPublicState', async () => { + const sim = await FungibleTokenSimulator.create( + NAME, + SYMBOL, + DECIMALS, + INIT, + ); - expect(sim.getPublicState()).toStrictEqual({}); + expect(await sim.getPublicState()).toStrictEqual({}); }); }); describe('privateState helpers', () => { describe('getCurrentSecretKey', () => { - it('should return the injected secret key', () => { - const sim = new FungibleTokenSimulator(NAME, SYMBOL, DECIMALS, INIT); - sim.privateState.injectSecretKey(OWNER.secretKey); + it('should return the injected secret key', async () => { + const sim = await FungibleTokenSimulator.create( + NAME, + SYMBOL, + DECIMALS, + INIT, + ); + await sim.privateState.injectSecretKey(OWNER.secretKey); - expect(sim.privateState.getCurrentSecretKey()).toEqual(OWNER.secretKey); + expect(await sim.privateState.getCurrentSecretKey()).toEqual( + OWNER.secretKey, + ); }); - it('should throw when the secret key is undefined', () => { - const sim = new FungibleTokenSimulator(NAME, SYMBOL, DECIMALS, INIT, { - privateState: { secretKey: undefined as unknown as Uint8Array }, - }); + it('should throw when the secret key is undefined', async () => { + const sim = await FungibleTokenSimulator.create( + NAME, + SYMBOL, + DECIMALS, + INIT, + { + privateState: { secretKey: undefined as unknown as Uint8Array }, + }, + ); - expect(() => sim.privateState.getCurrentSecretKey()).toThrow( + await expect(sim.privateState.getCurrentSecretKey()).rejects.toThrow( 'Missing secret key', ); }); diff --git a/contracts/src/token/test/MultiToken.test.ts b/contracts/src/token/test/MultiToken.test.ts index 1807308c..88d955b6 100644 --- a/contracts/src/token/test/MultiToken.test.ts +++ b/contracts/src/token/test/MultiToken.test.ts @@ -125,30 +125,30 @@ let token: MultiTokenSimulator; describe('MultiToken', () => { describe('before initialization', () => { - it('should initialize metadata', () => { - token = new MultiTokenSimulator(initWithURI); + it('should initialize metadata', async () => { + token = await MultiTokenSimulator.create(initWithURI); - expect(token.uri(TOKEN_ID)).toEqual(URI); + expect(await token.uri(TOKEN_ID)).toEqual(URI); }); - it('should initialize empty metadata', () => { - token = new MultiTokenSimulator(initWithEmptyURI); + it('should initialize empty metadata', async () => { + token = await MultiTokenSimulator.create(initWithEmptyURI); - expect(token.uri(TOKEN_ID)).toEqual(NO_STRING); + expect(await token.uri(TOKEN_ID)).toEqual(NO_STRING); }); - it('should not be able to re-initialize', () => { - token = new MultiTokenSimulator(initWithEmptyURI); + it('should not be able to re-initialize', async () => { + token = await MultiTokenSimulator.create(initWithEmptyURI); - expect(() => { - token.initialize(URI); - }).toThrow('MultiToken: contract already initialized'); + await expect(token.initialize(URI)).rejects.toThrow( + 'MultiToken: contract already initialized', + ); }); }); describe('when not initialized correctly', () => { - beforeEach(() => { - token = new MultiTokenSimulator(badInit); + beforeEach(async () => { + token = await MultiTokenSimulator.create(badInit); }); type FailingCircuits = [method: keyof MultiTokenSimulator, args: unknown[]]; @@ -169,24 +169,26 @@ describe('MultiToken', () => { ['_setApprovalForAll', [OWNER.either, SPENDER.either, true]], ]; - it.each(circuitsToFail)('%s should fail', (circuitName, args) => { - expect(() => { - (token[circuitName] as (...args: unknown[]) => unknown)(...args); - }).toThrow('MultiToken: contract not initialized'); + it.each(circuitsToFail)('%s should fail', async (circuitName, args) => { + await expect( + (token[circuitName] as (...args: unknown[]) => Promise)( + ...args, + ), + ).rejects.toThrow('MultiToken: contract not initialized'); }); - it('should allow initialization post deployment', () => { - token.initialize(URI); + it('should allow initialization post deployment', async () => { + await token.initialize(URI); - expect(() => { - token.balanceOf(OWNER.either, TOKEN_ID); - }).not.toThrow(); + await expect( + token.balanceOf(OWNER.either, TOKEN_ID), + ).resolves.not.toThrow(); }); }); describe('when initialized correctly', () => { - beforeEach(() => { - token = new MultiTokenSimulator(initWithURI); + beforeEach(async () => { + token = await MultiTokenSimulator.create(initWithURI); }); describe('balanceOf', () => { @@ -196,980 +198,1128 @@ describe('MultiToken', () => { ] as const; describe.each(ownerTypes)('when the owner is a %s', (_, owner) => { - it('should return zero when requested account has no balance', () => { - expect(token.balanceOf(owner, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(owner, TOKEN_ID2)).toEqual(0n); + it('should return zero when requested account has no balance', async () => { + expect(await token.balanceOf(owner, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(owner, TOKEN_ID2)).toEqual(0n); }); - it('should return balance when requested account has tokens', () => { - token._unsafeMint(owner, TOKEN_ID, AMOUNT); - expect(token.balanceOf(owner, TOKEN_ID)).toEqual(AMOUNT); + it('should return balance when requested account has tokens', async () => { + await token._unsafeMint(owner, TOKEN_ID, AMOUNT); + expect(await token.balanceOf(owner, TOKEN_ID)).toEqual(AMOUNT); - token._unsafeMint(owner, TOKEN_ID2, AMOUNT2); - expect(token.balanceOf(owner, TOKEN_ID2)).toEqual(AMOUNT2); + await token._unsafeMint(owner, TOKEN_ID2, AMOUNT2); + expect(await token.balanceOf(owner, TOKEN_ID2)).toEqual(AMOUNT2); }); - it('should handle token ID 0', () => { + it('should handle token ID 0', async () => { const ZERO_ID = 0n; - token._unsafeMint(owner, ZERO_ID, AMOUNT); - expect(token.balanceOf(owner, ZERO_ID)).toEqual(AMOUNT); + await token._unsafeMint(owner, ZERO_ID, AMOUNT); + expect(await token.balanceOf(owner, ZERO_ID)).toEqual(AMOUNT); }); - it('should handle MAX_UINT128 token ID', () => { + it('should handle MAX_UINT128 token ID', async () => { const MAX_ID = MAX_UINT128; - token._unsafeMint(owner, MAX_ID, AMOUNT); - expect(token.balanceOf(owner, MAX_ID)).toEqual(AMOUNT); + await token._unsafeMint(owner, MAX_ID, AMOUNT); + expect(await token.balanceOf(owner, MAX_ID)).toEqual(AMOUNT); }); }); - it('should return correct balance with non-canonical lookup (left)', () => { - token._unsafeMint(OWNER.either, TOKEN_ID, AMOUNT); + it('should return correct balance with non-canonical lookup (left)', async () => { + await token._unsafeMint(OWNER.either, TOKEN_ID, AMOUNT); const nonCanonical = nonCanonicalLeft(OWNER.accountId); - expect(token.balanceOf(nonCanonical, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(nonCanonical, TOKEN_ID)).toEqual(AMOUNT); }); - it('should return correct balance with non-canonical lookup (right)', () => { - token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); + it('should return correct balance with non-canonical lookup (right)', async () => { + await token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); const nonCanonical = nonCanonicalRight(OWNER_CONTRACT.right); - expect(token.balanceOf(nonCanonical, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(nonCanonical, TOKEN_ID)).toEqual(AMOUNT); }); }); describe('isApprovedForAll', () => { - it('should return false when not set', () => { - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + it('should return false when not set', async () => { + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( false, ); }); - it('should handle approving owner as operator', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(OWNER.either, true); - expect(token.isApprovedForAll(OWNER.either, OWNER.either)).toBe(true); + it('should handle approving owner as operator', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(OWNER.either, true); + expect(await token.isApprovedForAll(OWNER.either, OWNER.either)).toBe( + true, + ); }); - it('should handle multiple approvals of same operator', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + it('should handle multiple approvals of same operator', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); + await token.setApprovalForAll(SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); }); - it('should handle revoking non-existent approval', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + it('should handle revoking non-existent approval', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, false); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( false, ); }); - it('should return correct result with non-canonical owner lookup', () => { - token._setApprovalForAll(OWNER.either, SPENDER.either, true); + it('should return correct result with non-canonical owner lookup', async () => { + await token._setApprovalForAll(OWNER.either, SPENDER.either, true); const nonCanonical = nonCanonicalLeft(OWNER.accountId); - expect(token.isApprovedForAll(nonCanonical, SPENDER.either)).toBe(true); + expect(await token.isApprovedForAll(nonCanonical, SPENDER.either)).toBe( + true, + ); }); - it('should return correct result with non-canonical operator lookup', () => { - token._setApprovalForAll(OWNER.either, SPENDER.either, true); + it('should return correct result with non-canonical operator lookup', async () => { + await token._setApprovalForAll(OWNER.either, SPENDER.either, true); const nonCanonical = nonCanonicalLeft(SPENDER.accountId); - expect(token.isApprovedForAll(OWNER.either, nonCanonical)).toBe(true); + expect(await token.isApprovedForAll(OWNER.either, nonCanonical)).toBe( + true, + ); }); }); describe('setApprovalForAll', () => { - it('should return false when set to false', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + it('should return false when set to false', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, false); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( false, ); }); - it('should fail when attempting to approve zero address as an operator', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.setApprovalForAll(ZERO_ACCOUNT, true); - }).toThrow('MultiToken: invalid operator'); + it('should fail when attempting to approve zero address as an operator', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token.setApprovalForAll(ZERO_ACCOUNT, true), + ).rejects.toThrow('MultiToken: invalid operator'); }); describe('when spender is approved as an operator', () => { - beforeEach(() => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + beforeEach(async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); }); - it('should return true when set to true', () => { - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( - true, - ); + it('should return true when set to true', async () => { + expect( + await token.isApprovedForAll(OWNER.either, SPENDER.either), + ).toBe(true); }); - it('should unset → set → unset operator', () => { - token.setApprovalForAll(SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( - false, - ); - - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( - true, - ); - - token.setApprovalForAll(SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( - false, - ); + it('should unset → set → unset operator', async () => { + await token.setApprovalForAll(SPENDER.either, false); + expect( + await token.isApprovedForAll(OWNER.either, SPENDER.either), + ).toBe(false); + + await token.setApprovalForAll(SPENDER.either, true); + expect( + await token.isApprovedForAll(OWNER.either, SPENDER.either), + ).toBe(true); + + await token.setApprovalForAll(SPENDER.either, false); + expect( + await token.isApprovedForAll(OWNER.either, SPENDER.either), + ).toBe(false); }); }); }); describe('transferFrom', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKEN_ID, AMOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); }); describe.each(callerTypes)('when the caller is the %s', (_, caller) => { - beforeEach(() => { + beforeEach(async () => { if (caller === SPENDER) { - token._setApprovalForAll(OWNER.either, SPENDER.either, true); + await token._setApprovalForAll(OWNER.either, SPENDER.either, true); } - token.privateState.injectSecretKey(caller.secretKey); + await token.privateState.injectSecretKey(caller.secretKey); }); - it('should transfer whole', () => { - token.transferFrom(OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT); + it('should transfer whole', async () => { + await token.transferFrom( + OWNER.either, + RECIPIENT.either, + TOKEN_ID, + AMOUNT, + ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should transfer partial', () => { + it('should transfer partial', async () => { const partialAmt = AMOUNT - 1n; - token.transferFrom( + await token.transferFrom( OWNER.either, RECIPIENT.either, TOKEN_ID, partialAmt, ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( AMOUNT - partialAmt, ); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( partialAmt, ); }); - it('should allow transfer of 0 tokens', () => { - token.transferFrom(OWNER.either, RECIPIENT.either, TOKEN_ID, 0n); + it('should allow transfer of 0 tokens', async () => { + await token.transferFrom( + OWNER.either, + RECIPIENT.either, + TOKEN_ID, + 0n, + ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); }); - it('should handle self-transfer', () => { - token.transferFrom(OWNER.either, OWNER.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); + it('should handle self-transfer', async () => { + await token.transferFrom( + OWNER.either, + OWNER.either, + TOKEN_ID, + AMOUNT, + ); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); }); - it('should handle MAX_UINT128 transfer amount', () => { - token._mint(OWNER.either, TOKEN_ID, MAX_UINT128 - AMOUNT); + it('should handle MAX_UINT128 transfer amount', async () => { + await token._mint(OWNER.either, TOKEN_ID, MAX_UINT128 - AMOUNT); - token.transferFrom( + await token.transferFrom( OWNER.either, RECIPIENT.either, TOKEN_ID, MAX_UINT128, ); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( MAX_UINT128, ); }); - it('should handle rapid state changes', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + it('should handle rapid state changes', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.transferFrom(OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); - - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( - false, + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.transferFrom( + OWNER.either, + RECIPIENT.either, + TOKEN_ID, + AMOUNT, ); - - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( - true, + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, ); + + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, false); + expect( + await token.isApprovedForAll(OWNER.either, SPENDER.either), + ).toBe(false); + + await token.setApprovalForAll(SPENDER.either, true); + expect( + await token.isApprovedForAll(OWNER.either, SPENDER.either), + ).toBe(true); }); - it('should fail with insufficient balance', () => { - expect(() => { + it('should fail with insufficient balance', async () => { + await expect( token.transferFrom( OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT + 1n, - ); - }).toThrow('MultiToken: insufficient balance'); + ), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should fail with nonexistent id', () => { - expect(() => { + it('should fail with nonexistent id', async () => { + await expect( token.transferFrom( OWNER.either, RECIPIENT.either, NONEXISTENT_ID, AMOUNT, - ); - }).toThrow('MultiToken: insufficient balance'); + ), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should fail with transfer from zero', () => { - expect(() => { + it('should fail with transfer from zero', async () => { + await expect( token.transferFrom( ZERO_ACCOUNT, RECIPIENT.either, TOKEN_ID, AMOUNT, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail with transfer to zero (id)', () => { - expect(() => { - token.transferFrom(OWNER.either, ZERO_ACCOUNT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid receiver'); + it('should fail with transfer to zero (id)', async () => { + await expect( + token.transferFrom(OWNER.either, ZERO_ACCOUNT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid receiver'); }); - it('should fail with transfer to zero (contract)', () => { - expect(() => { - token.transferFrom(OWNER.either, ZERO_CONTRACT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: unsafe transfer'); + it('should fail with transfer to zero (contract)', async () => { + await expect( + token.transferFrom(OWNER.either, ZERO_CONTRACT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: unsafe transfer'); }); - it('should fail when transferring to a contract address', () => { - expect(() => { + it('should fail when transferring to a contract address', async () => { + await expect( token.transferFrom( OWNER.either, RECIPIENT_CONTRACT, TOKEN_ID, AMOUNT, - ); - }).toThrow('MultiToken: unsafe transfer'); + ), + ).rejects.toThrow('MultiToken: unsafe transfer'); }); }); - it('should handle concurrent operations on same token ID', () => { - token._mint(OWNER.either, TOKEN_ID, AMOUNT * 2n); + it('should handle concurrent operations on same token ID', async () => { + await token._mint(OWNER.either, TOKEN_ID, AMOUNT * 2n); - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - token.setApprovalForAll(OTHER.either, true); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); + await token.setApprovalForAll(OTHER.either, true); // First spender transfers half - token.privateState.injectSecretKey(SPENDER.secretKey); - token.transferFrom(OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.transferFrom( + OWNER.either, + RECIPIENT.either, + TOKEN_ID, + AMOUNT, + ); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); // Second spender transfers remaining - token.privateState.injectSecretKey(OTHER.secretKey); - token.transferFrom(OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + await token.privateState.injectSecretKey(OTHER.secretKey); + await token.transferFrom( + OWNER.either, + RECIPIENT.either, + TOKEN_ID, + AMOUNT, + ); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( AMOUNT * 2n, ); }); - it('should handle non-canonical fromAddress (id)', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should handle non-canonical fromAddress (id)', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); const nonCanonical = nonCanonicalLeft(OWNER.accountId); - token.transferFrom(nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT); + await token.transferFrom( + nonCanonical, + RECIPIENT.either, + TOKEN_ID, + AMOUNT, + ); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should handle non-canonical fromAddress (contract address)', () => { - token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); - token._setApprovalForAll(OWNER_CONTRACT, OWNER.either, true); + it('should handle non-canonical fromAddress (contract address)', async () => { + await token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); + await token._setApprovalForAll(OWNER_CONTRACT, OWNER.either, true); - token.privateState.injectSecretKey(OWNER.secretKey); + await token.privateState.injectSecretKey(OWNER.secretKey); const nonCanonical = nonCanonicalRight(OWNER_CONTRACT.right); - token.transferFrom(nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT); + await token.transferFrom( + nonCanonical, + RECIPIENT.either, + TOKEN_ID, + AMOUNT, + ); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); describe('when the caller is unauthorized', () => { - beforeEach(() => { - token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + beforeEach(async () => { + await token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); }); - it('should fail when transfer whole', () => { - expect(() => { + it('should fail when transfer whole', async () => { + await expect( token.transferFrom( OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail when transfer partial', () => { - expect(() => { - const partialAmt = AMOUNT - 1n; + it('should fail when transfer partial', async () => { + const partialAmt = AMOUNT - 1n; + await expect( token.transferFrom( OWNER.either, RECIPIENT.either, TOKEN_ID, partialAmt, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail when transfer zero', () => { - expect(() => { - token.transferFrom(OWNER.either, RECIPIENT.either, TOKEN_ID, 0n); - }).toThrow('MultiToken: unauthorized operator'); + it('should fail when transfer zero', async () => { + await expect( + token.transferFrom(OWNER.either, RECIPIENT.either, TOKEN_ID, 0n), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail with insufficient balance', () => { - expect(() => { + it('should fail with insufficient balance', async () => { + await expect( token.transferFrom( OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT + 1n, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail with nonexistent id', () => { - expect(() => { + it('should fail with nonexistent id', async () => { + await expect( token.transferFrom( OWNER.either, RECIPIENT.either, NONEXISTENT_ID, AMOUNT, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail with transfer from zero', () => { - expect(() => { + it('should fail with transfer from zero', async () => { + await expect( token.transferFrom( ZERO_ACCOUNT, RECIPIENT.either, TOKEN_ID, AMOUNT, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); }); }); describe('_unsafeTransferFrom', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKEN_ID, AMOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, TOKEN_ID, AMOUNT); }); describe.each(callerTypes)('when the caller is the %s', (_, caller) => { - beforeEach(() => { + beforeEach(async () => { if (caller === SPENDER) { - token._setApprovalForAll(OWNER.either, SPENDER.either, true); + await token._setApprovalForAll(OWNER.either, SPENDER.either, true); } - token.privateState.injectSecretKey(caller.secretKey); + await token.privateState.injectSecretKey(caller.secretKey); }); describe.each( recipientTypes, )('when the recipient is a %s', (_, recipient) => { - it('should transfer whole', () => { - token._unsafeTransferFrom( + it('should transfer whole', async () => { + await token._unsafeTransferFrom( OWNER.either, recipient, TOKEN_ID, AMOUNT, ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual(AMOUNT); }); - it('should transfer partial', () => { + it('should transfer partial', async () => { const partialAmt = AMOUNT - 1n; - token._unsafeTransferFrom( + await token._unsafeTransferFrom( OWNER.either, recipient, TOKEN_ID, partialAmt, ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( AMOUNT - partialAmt, ); - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(partialAmt); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual( + partialAmt, + ); }); - it('should allow transfer of 0 tokens', () => { - token._unsafeTransferFrom(OWNER.either, recipient, TOKEN_ID, 0n); + it('should allow transfer of 0 tokens', async () => { + await token._unsafeTransferFrom( + OWNER.either, + recipient, + TOKEN_ID, + 0n, + ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( + AMOUNT, + ); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual(0n); }); - it('should handle self-transfer', () => { - token._unsafeTransferFrom( + it('should handle self-transfer', async () => { + await token._unsafeTransferFrom( OWNER.either, OWNER.either, TOKEN_ID, AMOUNT, ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should handle MAX_UINT128 transfer amount', () => { - token._mint(OWNER.either, TOKEN_ID, MAX_UINT128 - AMOUNT); + it('should handle MAX_UINT128 transfer amount', async () => { + await token._mint(OWNER.either, TOKEN_ID, MAX_UINT128 - AMOUNT); - token._unsafeTransferFrom( + await token._unsafeTransferFrom( OWNER.either, recipient, TOKEN_ID, MAX_UINT128, ); - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(MAX_UINT128); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual( + MAX_UINT128, + ); }); - it('should handle rapid state changes', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + it('should handle rapid state changes', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); - token._unsafeTransferFrom( + await token._unsafeTransferFrom( OWNER.either, recipient, TOKEN_ID, AMOUNT, ); - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual(AMOUNT); - token.setApprovalForAll(SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( - false, - ); + await token.setApprovalForAll(SPENDER.either, false); + expect( + await token.isApprovedForAll(OWNER.either, SPENDER.either), + ).toBe(false); - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( - true, - ); + await token.setApprovalForAll(SPENDER.either, true); + expect( + await token.isApprovedForAll(OWNER.either, SPENDER.either), + ).toBe(true); }); - it('should fail with insufficient balance', () => { - expect(() => { + it('should fail with insufficient balance', async () => { + await expect( token._unsafeTransferFrom( OWNER.either, recipient, TOKEN_ID, AMOUNT + 1n, - ); - }).toThrow('MultiToken: insufficient balance'); + ), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should fail with nonexistent id', () => { - expect(() => { + it('should fail with nonexistent id', async () => { + await expect( token._unsafeTransferFrom( OWNER.either, recipient, NONEXISTENT_ID, AMOUNT, - ); - }).toThrow('MultiToken: insufficient balance'); + ), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should fail with transfer from zero', () => { - expect(() => { + it('should fail with transfer from zero', async () => { + await expect( token._unsafeTransferFrom( ZERO_ACCOUNT, recipient, TOKEN_ID, AMOUNT, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); }); - it('should fail with transfer to zero (id)', () => { - expect(() => { + it('should fail with transfer to zero (id)', async () => { + await expect( token._unsafeTransferFrom( OWNER.either, ZERO_ACCOUNT, TOKEN_ID, AMOUNT, - ); - }).toThrow('MultiToken: invalid receiver'); + ), + ).rejects.toThrow('MultiToken: invalid receiver'); }); - it('should fail with transfer to zero (contract)', () => { - expect(() => { + it('should fail with transfer to zero (contract)', async () => { + await expect( token._unsafeTransferFrom( OWNER.either, ZERO_CONTRACT, TOKEN_ID, AMOUNT, - ); - }).toThrow('MultiToken: invalid receiver'); + ), + ).rejects.toThrow('MultiToken: invalid receiver'); }); }); - it('should handle concurrent operations on same token ID', () => { - token._mint(OWNER.either, TOKEN_ID, AMOUNT * 2n); + it('should handle concurrent operations on same token ID', async () => { + await token._mint(OWNER.either, TOKEN_ID, AMOUNT * 2n); - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - token.setApprovalForAll(OTHER.either, true); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); + await token.setApprovalForAll(OTHER.either, true); // First spender transfers half - token.privateState.injectSecretKey(SPENDER.secretKey); - token._unsafeTransferFrom( + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token._unsafeTransferFrom( OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT, ); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); // Second spender transfers remaining - token.privateState.injectSecretKey(OTHER.secretKey); - token._unsafeTransferFrom( + await token.privateState.injectSecretKey(OTHER.secretKey); + await token._unsafeTransferFrom( OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT, ); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( AMOUNT * 2n, ); }); - it('should handle non-canonical fromAddress (id)', () => { + it('should handle non-canonical fromAddress (id)', async () => { const nonCanonical = nonCanonicalLeft(OWNER.accountId); - token.privateState.injectSecretKey(OWNER.secretKey); - token._unsafeTransferFrom( + await token.privateState.injectSecretKey(OWNER.secretKey); + await token._unsafeTransferFrom( nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT, ); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should handle non-canonical fromAddress (contract address)', () => { + it('should handle non-canonical fromAddress (contract address)', async () => { // Mint to contract address to test the transfer of non-canonical `fromAddress` - token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); + await token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); // Approve owner (id) to move OWNER_CONTRACT's token - token._setApprovalForAll(OWNER_CONTRACT, OWNER.either, true); + await token._setApprovalForAll(OWNER_CONTRACT, OWNER.either, true); - token.privateState.injectSecretKey(OWNER.secretKey); + await token.privateState.injectSecretKey(OWNER.secretKey); const nonCanonical = nonCanonicalRight(OWNER_CONTRACT.right); - token._unsafeTransferFrom( + await token._unsafeTransferFrom( nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT, ); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should canonicalize recipient (id)', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should canonicalize recipient (id)', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); const nonCanonical = nonCanonicalLeft(RECIPIENT.accountId); - token._unsafeTransferFrom(OWNER.either, nonCanonical, TOKEN_ID, AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + await token._unsafeTransferFrom( + OWNER.either, + nonCanonical, + TOKEN_ID, + AMOUNT, + ); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should canonicalize recipient (contract address)', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should canonicalize recipient (contract address)', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); const nonCanonical = nonCanonicalRight(RECIPIENT_CONTRACT.right); - token._unsafeTransferFrom(OWNER.either, nonCanonical, TOKEN_ID, AMOUNT); - expect(token.balanceOf(RECIPIENT_CONTRACT, TOKEN_ID)).toEqual(AMOUNT); + await token._unsafeTransferFrom( + OWNER.either, + nonCanonical, + TOKEN_ID, + AMOUNT, + ); + expect(await token.balanceOf(RECIPIENT_CONTRACT, TOKEN_ID)).toEqual( + AMOUNT, + ); }); describe('when the caller is unauthorized', () => { - beforeEach(() => { - token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + beforeEach(async () => { + await token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); }); describe.each( recipientTypes, )('when recipient is %s', (_, recipient) => { - it('should fail when transfer whole', () => { - expect(() => { + it('should fail when transfer whole', async () => { + await expect( token._unsafeTransferFrom( OWNER.either, recipient, TOKEN_ID, AMOUNT, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail when transfer partial', () => { - expect(() => { - const partialAmt = AMOUNT - 1n; + it('should fail when transfer partial', async () => { + const partialAmt = AMOUNT - 1n; + await expect( token._unsafeTransferFrom( OWNER.either, recipient, TOKEN_ID, partialAmt, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail when transfer zero', () => { - expect(() => { - token._unsafeTransferFrom(OWNER.either, recipient, TOKEN_ID, 0n); - }).toThrow('MultiToken: unauthorized operator'); + it('should fail when transfer zero', async () => { + await expect( + token._unsafeTransferFrom(OWNER.either, recipient, TOKEN_ID, 0n), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail with insufficient balance', () => { - expect(() => { + it('should fail with insufficient balance', async () => { + await expect( token._unsafeTransferFrom( OWNER.either, recipient, TOKEN_ID, AMOUNT + 1n, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail with nonexistent id', () => { - expect(() => { + it('should fail with nonexistent id', async () => { + await expect( token._unsafeTransferFrom( OWNER.either, recipient, NONEXISTENT_ID, AMOUNT, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); - it('should fail with transfer from zero', () => { + it('should fail with transfer from zero', async () => { // With witness-based identity, the caller is H(sk) which is // always non-zero. Transferring from ZERO_ACCOUNT means // canonFrom != caller → isApprovedForAll(zeroAccount, caller) → false // → "unauthorized operator" - expect(() => { + await expect( token._unsafeTransferFrom( ZERO_ACCOUNT, recipient, TOKEN_ID, AMOUNT, - ); - }).toThrow('MultiToken: unauthorized operator'); + ), + ).rejects.toThrow('MultiToken: unauthorized operator'); }); }); }); }); describe('_transfer', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKEN_ID, AMOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); }); - it('should transfer whole', () => { - token._transfer(OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT); + it('should transfer whole', async () => { + await token._transfer(OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should transfer partial', () => { + it('should transfer partial', async () => { const partialAmt = AMOUNT - 1n; - token._transfer(OWNER.either, RECIPIENT.either, TOKEN_ID, partialAmt); + await token._transfer( + OWNER.either, + RECIPIENT.either, + TOKEN_ID, + partialAmt, + ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( AMOUNT - partialAmt, ); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(partialAmt); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + partialAmt, + ); }); - it('should allow transfer of 0 tokens', () => { - token._transfer(OWNER.either, RECIPIENT.either, TOKEN_ID, 0n); + it('should allow transfer of 0 tokens', async () => { + await token._transfer(OWNER.either, RECIPIENT.either, TOKEN_ID, 0n); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); }); - it('should fail with insufficient balance', () => { - expect(() => { + it('should fail with insufficient balance', async () => { + await expect( token._transfer( OWNER.either, RECIPIENT.either, TOKEN_ID, AMOUNT + 1n, - ); - }).toThrow('MultiToken: insufficient balance'); + ), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should fail with nonexistent id', () => { - expect(() => { + it('should fail with nonexistent id', async () => { + await expect( token._transfer( OWNER.either, RECIPIENT.either, NONEXISTENT_ID, AMOUNT, - ); - }).toThrow('MultiToken: insufficient balance'); + ), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should fail when transfer from 0', () => { - expect(() => { - token._transfer(ZERO_ACCOUNT, RECIPIENT.either, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid sender'); + it('should fail when transfer from 0', async () => { + await expect( + token._transfer(ZERO_ACCOUNT, RECIPIENT.either, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid sender'); }); - it('should fail when transfer to 0', () => { - expect(() => { - token._transfer(OWNER.either, ZERO_ACCOUNT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid receiver'); + it('should fail when transfer to 0', async () => { + await expect( + token._transfer(OWNER.either, ZERO_ACCOUNT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid receiver'); }); - it('should fail when transfer to contract address', () => { - expect(() => { - token._transfer(OWNER.either, RECIPIENT_CONTRACT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: unsafe transfer'); + it('should fail when transfer to contract address', async () => { + await expect( + token._transfer(OWNER.either, RECIPIENT_CONTRACT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: unsafe transfer'); }); - it('should handle non-canonical fromAddress (id)', () => { + it('should handle non-canonical fromAddress (id)', async () => { const nonCanonical = nonCanonicalLeft(OWNER.accountId); - token._transfer(nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + await token._transfer(nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should handle non-canonical fromAddress (contract address)', () => { - token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); + it('should handle non-canonical fromAddress (contract address)', async () => { + await token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); const nonCanonical = nonCanonicalRight(OWNER_CONTRACT.right); - token._transfer(nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT); + await token._transfer(nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER_CONTRACT, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER_CONTRACT, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); }); describe('_unsafeTransfer', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKEN_ID, AMOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(0n); }); describe.each( recipientTypes, )('when the recipient is a %s', (_, recipient) => { - it('should transfer whole', () => { - token._unsafeTransfer(OWNER.either, recipient, TOKEN_ID, AMOUNT); + it('should transfer whole', async () => { + await token._unsafeTransfer( + OWNER.either, + recipient, + TOKEN_ID, + AMOUNT, + ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual(AMOUNT); }); - it('should transfer partial', () => { + it('should transfer partial', async () => { const partialAmt = AMOUNT - 1n; - token._unsafeTransfer(OWNER.either, recipient, TOKEN_ID, partialAmt); + await token._unsafeTransfer( + OWNER.either, + recipient, + TOKEN_ID, + partialAmt, + ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( AMOUNT - partialAmt, ); - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(partialAmt); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual( + partialAmt, + ); }); - it('should allow transfer of 0 tokens', () => { - token._unsafeTransfer(OWNER.either, recipient, TOKEN_ID, 0n); + it('should allow transfer of 0 tokens', async () => { + await token._unsafeTransfer(OWNER.either, recipient, TOKEN_ID, 0n); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual(0n); }); - it('should fail with insufficient balance', () => { - expect(() => { + it('should fail with insufficient balance', async () => { + await expect( token._unsafeTransfer( OWNER.either, recipient, TOKEN_ID, AMOUNT + 1n, - ); - }).toThrow('MultiToken: insufficient balance'); + ), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should fail with nonexistent id', () => { - expect(() => { + it('should fail with nonexistent id', async () => { + await expect( token._unsafeTransfer( OWNER.either, recipient, NONEXISTENT_ID, AMOUNT, - ); - }).toThrow('MultiToken: insufficient balance'); + ), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should fail when transfer from 0 (id)', () => { - expect(() => { - token._unsafeTransfer(ZERO_ACCOUNT, recipient, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid sender'); + it('should fail when transfer from 0 (id)', async () => { + await expect( + token._unsafeTransfer(ZERO_ACCOUNT, recipient, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid sender'); }); - it('should fail when transfer from 0 (contract address)', () => { - expect(() => { - token._unsafeTransfer(ZERO_CONTRACT, recipient, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid sender'); + it('should fail when transfer from 0 (contract address)', async () => { + await expect( + token._unsafeTransfer(ZERO_CONTRACT, recipient, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid sender'); }); }); - it('should handle non-canonical fromAddress (id)', () => { + it('should handle non-canonical fromAddress (id)', async () => { const nonCanonical = nonCanonicalLeft(OWNER.accountId); - token._unsafeTransfer(nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT); + await token._unsafeTransfer( + nonCanonical, + RECIPIENT.either, + TOKEN_ID, + AMOUNT, + ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should handle non-canonical fromAddress (contract address)', () => { + it('should handle non-canonical fromAddress (contract address)', async () => { // Mint to contract address to test the transfer of non-canonical `fromAddress` - token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); + await token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); const nonCanonical = nonCanonicalRight(OWNER_CONTRACT.right); - token._unsafeTransfer(nonCanonical, RECIPIENT.either, TOKEN_ID, AMOUNT); + await token._unsafeTransfer( + nonCanonical, + RECIPIENT.either, + TOKEN_ID, + AMOUNT, + ); - expect(token.balanceOf(OWNER_CONTRACT, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER_CONTRACT, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should handle non-canonical to (id)', () => { + it('should handle non-canonical to (id)', async () => { const nonCanonical = nonCanonicalLeft(RECIPIENT.accountId); - token._unsafeTransfer(OWNER.either, nonCanonical, TOKEN_ID, AMOUNT); + await token._unsafeTransfer( + OWNER.either, + nonCanonical, + TOKEN_ID, + AMOUNT, + ); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should handle non-canonical to (contract address)', () => { + it('should handle non-canonical to (contract address)', async () => { const nonCanonical = nonCanonicalRight(RECIPIENT_CONTRACT.right); - token._unsafeTransfer(OWNER.either, nonCanonical, TOKEN_ID, AMOUNT); + await token._unsafeTransfer( + OWNER.either, + nonCanonical, + TOKEN_ID, + AMOUNT, + ); - expect(token.balanceOf(RECIPIENT_CONTRACT, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT_CONTRACT, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should fail when transfer to 0 (id)', () => { - expect(() => { - token._unsafeTransfer(OWNER.either, ZERO_ACCOUNT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid receiver'); + it('should fail when transfer to 0 (id)', async () => { + await expect( + token._unsafeTransfer(OWNER.either, ZERO_ACCOUNT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid receiver'); }); - it('should fail when transfer to 0 (contract address)', () => { - expect(() => { - token._unsafeTransfer(OWNER.either, ZERO_CONTRACT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid receiver'); + it('should fail when transfer to 0 (contract address)', async () => { + await expect( + token._unsafeTransfer(OWNER.either, ZERO_CONTRACT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid receiver'); }); }); describe('_setURI', () => { - it('sets a new URI', () => { - token._setURI(NEW_URI); + it('sets a new URI', async () => { + await token._setURI(NEW_URI); - expect(token.uri(TOKEN_ID)).toEqual(NEW_URI); - expect(token.uri(TOKEN_ID2)).toEqual(NEW_URI); + expect(await token.uri(TOKEN_ID)).toEqual(NEW_URI); + expect(await token.uri(TOKEN_ID2)).toEqual(NEW_URI); }); - it('sets an empty URI → newURI → empty URI → URI', () => { + it('sets an empty URI → newURI → empty URI → URI', async () => { const URIS = [NO_STRING, NEW_URI, NO_STRING, URI]; for (let i = 0; i < URIS.length; i++) { - token._setURI(URIS[i]); + await token._setURI(URIS[i]); - expect(token.uri(TOKEN_ID)).toEqual(URIS[i]); - expect(token.uri(TOKEN_ID2)).toEqual(URIS[i]); + expect(await token.uri(TOKEN_ID)).toEqual(URIS[i]); + expect(await token.uri(TOKEN_ID2)).toEqual(URIS[i]); } }); - it('should handle long URI', () => { + it('should handle long URI', async () => { const LONG_URI = `https://example.com/${'a'.repeat(1000)}`; - token._setURI(LONG_URI); - expect(token.uri(TOKEN_ID)).toEqual(LONG_URI); + await token._setURI(LONG_URI); + expect(await token.uri(TOKEN_ID)).toEqual(LONG_URI); }); - it('should handle URI with special characters', () => { + it('should handle URI with special characters', async () => { const SPECIAL_URI = 'https://example.com/path?param=value#fragment'; - token._setURI(SPECIAL_URI); - expect(token.uri(TOKEN_ID)).toEqual(SPECIAL_URI); + await token._setURI(SPECIAL_URI); + expect(await token.uri(TOKEN_ID)).toEqual(SPECIAL_URI); }); }); describe('_mint', () => { - it('should update balance when minting', () => { - token._mint(RECIPIENT.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + it('should update balance when minting', async () => { + await token._mint(RECIPIENT.either, TOKEN_ID, AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should update balance with multiple mints', () => { + it('should update balance with multiple mints', async () => { for (let i = 0; i < 3; i++) { - token._mint(RECIPIENT.either, TOKEN_ID, 1n); + await token._mint(RECIPIENT.either, TOKEN_ID, 1n); } - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(3n); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(3n); }); - it('should fail when overflowing uint128', () => { - token._mint(RECIPIENT.either, TOKEN_ID, MAX_UINT128); + it('should fail when overflowing uint128', async () => { + await token._mint(RECIPIENT.either, TOKEN_ID, MAX_UINT128); - expect(() => { - token._mint(RECIPIENT.either, TOKEN_ID, 1n); - }).toThrow('MultiToken: arithmetic overflow'); + await expect( + token._mint(RECIPIENT.either, TOKEN_ID, 1n), + ).rejects.toThrow('MultiToken: arithmetic overflow'); }); - it('should fail when minting to zero address (id)', () => { - expect(() => { - token._mint(ZERO_ACCOUNT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid receiver'); + it('should fail when minting to zero address (id)', async () => { + await expect( + token._mint(ZERO_ACCOUNT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid receiver'); }); - it('should fail when minting to zero address (contract)', () => { - expect(() => { - token._mint(ZERO_CONTRACT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: unsafe transfer'); + it('should fail when minting to zero address (contract)', async () => { + await expect( + token._mint(ZERO_CONTRACT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: unsafe transfer'); }); - it('should fail when minting to a contract address', () => { - expect(() => { - token._mint(RECIPIENT_CONTRACT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: unsafe transfer'); + it('should fail when minting to a contract address', async () => { + await expect( + token._mint(RECIPIENT_CONTRACT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: unsafe transfer'); }); - it('should canonicalize recipient', () => { + it('should canonicalize recipient', async () => { const nonCanonical = nonCanonicalLeft(RECIPIENT.accountId); - token._mint(nonCanonical, TOKEN_ID, AMOUNT); + await token._mint(nonCanonical, TOKEN_ID, AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); }); @@ -1177,167 +1327,179 @@ describe('MultiToken', () => { describe.each( recipientTypes, )('when the recipient is a %s', (_, recipient) => { - it('should update balance when minting', () => { - token._unsafeMint(recipient, TOKEN_ID, AMOUNT); + it('should update balance when minting', async () => { + await token._unsafeMint(recipient, TOKEN_ID, AMOUNT); - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual(AMOUNT); }); - it('should update balance with multiple mints', () => { + it('should update balance with multiple mints', async () => { for (let i = 0; i < 3; i++) { - token._unsafeMint(recipient, TOKEN_ID, 1n); + await token._unsafeMint(recipient, TOKEN_ID, 1n); } - expect(token.balanceOf(recipient, TOKEN_ID)).toEqual(3n); + expect(await token.balanceOf(recipient, TOKEN_ID)).toEqual(3n); }); - it('should fail when overflowing uint128', () => { - token._unsafeMint(recipient, TOKEN_ID, MAX_UINT128); + it('should fail when overflowing uint128', async () => { + await token._unsafeMint(recipient, TOKEN_ID, MAX_UINT128); - expect(() => { - token._unsafeMint(recipient, TOKEN_ID, 1n); - }).toThrow('MultiToken: arithmetic overflow'); + await expect( + token._unsafeMint(recipient, TOKEN_ID, 1n), + ).rejects.toThrow('MultiToken: arithmetic overflow'); }); }); - it('should fail when minting to zero address (id)', () => { - expect(() => { - token._unsafeMint(ZERO_ACCOUNT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid receiver'); + it('should fail when minting to zero address (id)', async () => { + await expect( + token._unsafeMint(ZERO_ACCOUNT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid receiver'); }); - it('should fail when minting to zero address (contract)', () => { - expect(() => { - token._unsafeMint(ZERO_CONTRACT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid receiver'); + it('should fail when minting to zero address (contract)', async () => { + await expect( + token._unsafeMint(ZERO_CONTRACT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid receiver'); }); - it('should canonicalize recipient', () => { + it('should canonicalize recipient', async () => { const nonCanonical = nonCanonicalLeft(RECIPIENT.accountId); - token._unsafeMint(nonCanonical, TOKEN_ID, AMOUNT); + await token._unsafeMint(nonCanonical, TOKEN_ID, AMOUNT); - expect(token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT.either, TOKEN_ID)).toEqual( + AMOUNT, + ); }); - it('should canonicalize contract address recipient', () => { + it('should canonicalize contract address recipient', async () => { const nonCanonical = nonCanonicalRight(RECIPIENT_CONTRACT.right); - token._unsafeMint(nonCanonical, TOKEN_ID, AMOUNT); + await token._unsafeMint(nonCanonical, TOKEN_ID, AMOUNT); - expect(token.balanceOf(RECIPIENT_CONTRACT, TOKEN_ID)).toEqual(AMOUNT); + expect(await token.balanceOf(RECIPIENT_CONTRACT, TOKEN_ID)).toEqual( + AMOUNT, + ); }); }); describe('_burn', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, TOKEN_ID, AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT); }); - it('should burn tokens', () => { - token._burn(OWNER.either, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); + it('should burn tokens', async () => { + await token._burn(OWNER.either, TOKEN_ID, AMOUNT); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); }); - it('should burn partial', () => { + it('should burn partial', async () => { const partialAmt = 1n; - token._burn(OWNER.either, TOKEN_ID, partialAmt); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( + await token._burn(OWNER.either, TOKEN_ID, partialAmt); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( AMOUNT - partialAmt, ); }); - it('should update balance with multiple burns', () => { + it('should update balance with multiple burns', async () => { for (let i = 0; i < 3; i++) { - token._burn(OWNER.either, TOKEN_ID, 1n); + await token._burn(OWNER.either, TOKEN_ID, 1n); } - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(AMOUNT - 3n); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual( + AMOUNT - 3n, + ); }); - it('should fail when not enough balance to burn', () => { - expect(() => { - token._burn(OWNER.either, TOKEN_ID, AMOUNT + 1n); - }).toThrow('MultiToken: insufficient balance'); + it('should fail when not enough balance to burn', async () => { + await expect( + token._burn(OWNER.either, TOKEN_ID, AMOUNT + 1n), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should fail when burning the zero address tokens', () => { - expect(() => { - token._burn(ZERO_ACCOUNT, TOKEN_ID, AMOUNT); - }).toThrow('MultiToken: invalid sender'); + it('should fail when burning the zero address tokens', async () => { + await expect( + token._burn(ZERO_ACCOUNT, TOKEN_ID, AMOUNT), + ).rejects.toThrow('MultiToken: invalid sender'); }); - it('should fail when burning tokens from nonexistent id', () => { - expect(() => { - token._burn(OWNER.either, NONEXISTENT_ID, AMOUNT); - }).toThrow('MultiToken: insufficient balance'); + it('should fail when burning tokens from nonexistent id', async () => { + await expect( + token._burn(OWNER.either, NONEXISTENT_ID, AMOUNT), + ).rejects.toThrow('MultiToken: insufficient balance'); }); - it('should handle non-canonical fromAddress (id)', () => { + it('should handle non-canonical fromAddress (id)', async () => { const nonCanonical = nonCanonicalLeft(OWNER.accountId); - token._burn(nonCanonical, TOKEN_ID, AMOUNT); + await token._burn(nonCanonical, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(OWNER.either, TOKEN_ID)).toEqual(0n); }); - it('should handle non-canonical fromAddress (contract address)', () => { - token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER_CONTRACT, TOKEN_ID)).toEqual(AMOUNT); + it('should handle non-canonical fromAddress (contract address)', async () => { + await token._unsafeMint(OWNER_CONTRACT, TOKEN_ID, AMOUNT); + expect(await token.balanceOf(OWNER_CONTRACT, TOKEN_ID)).toEqual(AMOUNT); const nonCanonical = nonCanonicalRight(OWNER_CONTRACT.right); - token._burn(nonCanonical, TOKEN_ID, AMOUNT); + await token._burn(nonCanonical, TOKEN_ID, AMOUNT); - expect(token.balanceOf(OWNER_CONTRACT, TOKEN_ID)).toEqual(0n); + expect(await token.balanceOf(OWNER_CONTRACT, TOKEN_ID)).toEqual(0n); }); }); describe('_setApprovalForAll', () => { - it('should return false when set to false', () => { - token._setApprovalForAll(OWNER.either, SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + it('should return false when set to false', async () => { + await token._setApprovalForAll(OWNER.either, SPENDER.either, false); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( false, ); }); - it('should fail when attempting to approve zero address as an operator', () => { - expect(() => { - token._setApprovalForAll(OWNER.either, ZERO_ACCOUNT, true); - }).toThrow('MultiToken: invalid operator'); + it('should fail when attempting to approve zero address as an operator', async () => { + await expect( + token._setApprovalForAll(OWNER.either, ZERO_ACCOUNT, true), + ).rejects.toThrow('MultiToken: invalid operator'); }); - it('should fail when owner is zero address', () => { - expect(() => { - token._setApprovalForAll(ZERO_ACCOUNT, SPENDER.either, true); - }).toThrow('MultiToken: invalid owner'); + it('should fail when owner is zero address', async () => { + await expect( + token._setApprovalForAll(ZERO_ACCOUNT, SPENDER.either, true), + ).rejects.toThrow('MultiToken: invalid owner'); }); - it('should set → unset → set operator', () => { - token._setApprovalForAll(OWNER.either, SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + it('should set → unset → set operator', async () => { + await token._setApprovalForAll(OWNER.either, SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); - token._setApprovalForAll(OWNER.either, SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + await token._setApprovalForAll(OWNER.either, SPENDER.either, false); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( false, ); - token._setApprovalForAll(OWNER.either, SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + await token._setApprovalForAll(OWNER.either, SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); }); - it('should canonicalize owner and operator', () => { + it('should canonicalize owner and operator', async () => { const nonCanonicalOwner = nonCanonicalLeft(OWNER.accountId); const nonCanonicalOp = nonCanonicalLeft(SPENDER.accountId); - token._setApprovalForAll(nonCanonicalOwner, nonCanonicalOp, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + await token._setApprovalForAll(nonCanonicalOwner, nonCanonicalOp, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); }); }); }); describe('simulator wiring', () => { - it('should expose the balances map via getPublicState', () => { - const sim = new MultiTokenSimulator(initWithURI); + it('should expose the balances map via getPublicState', async () => { + const sim = await MultiTokenSimulator.create(initWithURI); - const ledgerState = sim.getPublicState(); + const ledgerState = await sim.getPublicState(); expect(ledgerState.MultiToken__balances.isEmpty()).toBe(true); expect(ledgerState.MultiToken__balances.size()).toBe(0n); @@ -1346,19 +1508,21 @@ describe('MultiToken', () => { describe('privateState helpers', () => { describe('getCurrentSecretKey', () => { - it('should return the injected secret key', () => { - const sim = new MultiTokenSimulator(initWithURI); - sim.privateState.injectSecretKey(OWNER.secretKey); + it('should return the injected secret key', async () => { + const sim = await MultiTokenSimulator.create(initWithURI); + await sim.privateState.injectSecretKey(OWNER.secretKey); - expect(sim.privateState.getCurrentSecretKey()).toEqual(OWNER.secretKey); + expect(await sim.privateState.getCurrentSecretKey()).toEqual( + OWNER.secretKey, + ); }); - it('should throw when the secret key is undefined', () => { - const sim = new MultiTokenSimulator(initWithURI, { + it('should throw when the secret key is undefined', async () => { + const sim = await MultiTokenSimulator.create(initWithURI, { privateState: { secretKey: undefined as unknown as Uint8Array }, }); - expect(() => sim.privateState.getCurrentSecretKey()).toThrow( + await expect(sim.privateState.getCurrentSecretKey()).rejects.toThrow( 'Missing secret key', ); }); diff --git a/contracts/src/token/test/nonFungibleToken.test.ts b/contracts/src/token/test/nonFungibleToken.test.ts index 55a1f37e..95f411c6 100644 --- a/contracts/src/token/test/nonFungibleToken.test.ts +++ b/contracts/src/token/test/nonFungibleToken.test.ts @@ -83,81 +83,97 @@ let token: NonFungibleTokenSimulator; describe('NonFungibleToken', () => { describe('initializer and metadata', () => { - it('should initialize metadata', () => { - token = new NonFungibleTokenSimulator(NAME, SYMBOL, INIT); - expect(token.name()).toEqual(NAME); - expect(token.symbol()).toEqual(SYMBOL); + it('should initialize metadata', async () => { + token = await NonFungibleTokenSimulator.create(NAME, SYMBOL, INIT); + expect(await token.name()).toEqual(NAME); + expect(await token.symbol()).toEqual(SYMBOL); }); - it('should initialize empty metadata', () => { - token = new NonFungibleTokenSimulator(EMPTY_STRING, EMPTY_STRING, INIT); - expect(token.name()).toEqual(EMPTY_STRING); - expect(token.symbol()).toEqual(EMPTY_STRING); + it('should initialize empty metadata', async () => { + token = await NonFungibleTokenSimulator.create( + EMPTY_STRING, + EMPTY_STRING, + INIT, + ); + expect(await token.name()).toEqual(EMPTY_STRING); + expect(await token.symbol()).toEqual(EMPTY_STRING); }); - it('should initialize metadata with whitespace', () => { - token = new NonFungibleTokenSimulator(' NAME ', ' SYMBOL ', INIT); - expect(token.name()).toEqual(' NAME '); - expect(token.symbol()).toEqual(' SYMBOL '); + it('should initialize metadata with whitespace', async () => { + token = await NonFungibleTokenSimulator.create( + ' NAME ', + ' SYMBOL ', + INIT, + ); + expect(await token.name()).toEqual(' NAME '); + expect(await token.symbol()).toEqual(' SYMBOL '); }); - it('should initialize metadata with special characters', () => { - token = new NonFungibleTokenSimulator('NAME!@#', 'SYMBOL$%^', INIT); - expect(token.name()).toEqual('NAME!@#'); - expect(token.symbol()).toEqual('SYMBOL$%^'); + it('should initialize metadata with special characters', async () => { + token = await NonFungibleTokenSimulator.create( + 'NAME!@#', + 'SYMBOL$%^', + INIT, + ); + expect(await token.name()).toEqual('NAME!@#'); + expect(await token.symbol()).toEqual('SYMBOL$%^'); }); - it('should initialize metadata with very long strings', () => { + it('should initialize metadata with very long strings', async () => { const longName = 'A'.repeat(1000); const longSymbol = 'B'.repeat(1000); - token = new NonFungibleTokenSimulator(longName, longSymbol, INIT); - expect(token.name()).toEqual(longName); - expect(token.symbol()).toEqual(longSymbol); + token = await NonFungibleTokenSimulator.create( + longName, + longSymbol, + INIT, + ); + expect(await token.name()).toEqual(longName); + expect(await token.symbol()).toEqual(longSymbol); }); }); - beforeEach(() => { - token = new NonFungibleTokenSimulator(NAME, SYMBOL, INIT); + beforeEach(async () => { + token = await NonFungibleTokenSimulator.create(NAME, SYMBOL, INIT); }); describe('balanceOf', () => { - it('should return zero when requested account has no balance', () => { - expect(token.balanceOf(OWNER.either)).toEqual(0n); + it('should return zero when requested account has no balance', async () => { + expect(await token.balanceOf(OWNER.either)).toEqual(0n); }); - it('should return balance when requested account has tokens', () => { - token._mint(OWNER.either, AMOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(AMOUNT); + it('should return balance when requested account has tokens', async () => { + await token._mint(OWNER.either, AMOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(AMOUNT); }); - it('should return correct balance for multiple tokens', () => { - token._mint(OWNER.either, TOKENID_1); - token._mint(OWNER.either, TOKENID_2); - token._mint(OWNER.either, TOKENID_3); - expect(token.balanceOf(OWNER.either)).toEqual(3n); + it('should return correct balance for multiple tokens', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._mint(OWNER.either, TOKENID_2); + await token._mint(OWNER.either, TOKENID_3); + expect(await token.balanceOf(OWNER.either)).toEqual(3n); }); - it('should return correct balance after burning multiple tokens', () => { - token._mint(OWNER.either, TOKENID_1); - token._mint(OWNER.either, TOKENID_2); - token._mint(OWNER.either, TOKENID_3); - token._burn(TOKENID_1); - token._burn(TOKENID_2); - expect(token.balanceOf(OWNER.either)).toEqual(1n); + it('should return correct balance after burning multiple tokens', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._mint(OWNER.either, TOKENID_2); + await token._mint(OWNER.either, TOKENID_3); + await token._burn(TOKENID_1); + await token._burn(TOKENID_2); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); }); - it('should return correct balance after transferring multiple tokens', () => { - token._mint(OWNER.either, TOKENID_1); - token._mint(OWNER.either, TOKENID_2); - token._mint(OWNER.either, TOKENID_3); - token._transfer(OWNER.either, RECIPIENT.either, TOKENID_1); - token._transfer(OWNER.either, RECIPIENT.either, TOKENID_2); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(RECIPIENT.either)).toEqual(2n); + it('should return correct balance after transferring multiple tokens', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._mint(OWNER.either, TOKENID_2); + await token._mint(OWNER.either, TOKENID_3); + await token._transfer(OWNER.either, RECIPIENT.either, TOKENID_1); + await token._transfer(OWNER.either, RECIPIENT.either, TOKENID_2); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(RECIPIENT.either)).toEqual(2n); }); - it('should return correct balance with non-canonical lookup (left)', () => { - token._mint(OWNER.either, TOKENID_1); + it('should return correct balance with non-canonical lookup (left)', async () => { + await token._mint(OWNER.either, TOKENID_1); const nonCanonical = { is_left: true, @@ -165,11 +181,11 @@ describe('NonFungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - expect(token.balanceOf(nonCanonical)).toEqual(1n); + expect(await token.balanceOf(nonCanonical)).toEqual(1n); }); - it('should return correct balance with non-canonical lookup (right)', () => { - token._unsafeMint(SOME_CONTRACT, TOKENID_1); + it('should return correct balance with non-canonical lookup (right)', async () => { + await token._unsafeMint(SOME_CONTRACT, TOKENID_1); const nonCanonical = { is_left: false, @@ -177,322 +193,342 @@ describe('NonFungibleToken', () => { right: SOME_CONTRACT.right, }; - expect(token.balanceOf(nonCanonical)).toEqual(1n); + expect(await token.balanceOf(nonCanonical)).toEqual(1n); }); }); describe('ownerOf', () => { - it('should throw if token does not exist', () => { - expect(() => { - token.ownerOf(NON_EXISTENT_TOKEN); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token does not exist', async () => { + await expect(token.ownerOf(NON_EXISTENT_TOKEN)).rejects.toThrow( + 'NonFungibleToken: nonexistent token', + ); }); - it('should throw if token has been burned', () => { - token._mint(OWNER.either, TOKENID_1); - token._burn(TOKENID_1); - expect(() => { - token.ownerOf(TOKENID_1); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token has been burned', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._burn(TOKENID_1); + await expect(token.ownerOf(TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: nonexistent token', + ); }); - it('should return owner of token if it exists', () => { - token._mint(OWNER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(OWNER.either); + it('should return owner of token if it exists', async () => { + await token._mint(OWNER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(OWNER.either); }); - it('should return correct owner for multiple tokens', () => { - token._mint(OWNER.either, TOKENID_1); - token._mint(OWNER.either, TOKENID_2); - token._mint(OWNER.either, TOKENID_3); - expect(token.ownerOf(TOKENID_1)).toEqual(OWNER.either); - expect(token.ownerOf(TOKENID_2)).toEqual(OWNER.either); - expect(token.ownerOf(TOKENID_3)).toEqual(OWNER.either); + it('should return correct owner for multiple tokens', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._mint(OWNER.either, TOKENID_2); + await token._mint(OWNER.either, TOKENID_3); + expect(await token.ownerOf(TOKENID_1)).toEqual(OWNER.either); + expect(await token.ownerOf(TOKENID_2)).toEqual(OWNER.either); + expect(await token.ownerOf(TOKENID_3)).toEqual(OWNER.either); }); - it('should return correct owner after multiple transfers', () => { - token._mint(OWNER.either, TOKENID_1); - token._mint(OWNER.either, TOKENID_2); - token._transfer(OWNER.either, SPENDER.either, TOKENID_1); - token._transfer(OWNER.either, OTHER.either, TOKENID_2); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); - expect(token.ownerOf(TOKENID_2)).toEqual(OTHER.either); + it('should return correct owner after multiple transfers', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._mint(OWNER.either, TOKENID_2); + await token._transfer(OWNER.either, SPENDER.either, TOKENID_1); + await token._transfer(OWNER.either, OTHER.either, TOKENID_2); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + expect(await token.ownerOf(TOKENID_2)).toEqual(OTHER.either); }); - it('should return correct owner after multiple burns and mints', () => { - token._mint(OWNER.either, TOKENID_1); - token._burn(TOKENID_1); - token._mint(SPENDER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + it('should return correct owner after multiple burns and mints', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._burn(TOKENID_1); + await token._mint(SPENDER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); }); }); describe('tokenURI', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKENID_1); + beforeEach(async () => { + await token._mint(OWNER.either, TOKENID_1); }); - it('should throw if token does not exist', () => { - expect(() => { - token.tokenURI(NON_EXISTENT_TOKEN); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token does not exist', async () => { + await expect(token.tokenURI(NON_EXISTENT_TOKEN)).rejects.toThrow( + 'NonFungibleToken: nonexistent token', + ); }); - it('should return the empty string for an unset tokenURI', () => { - expect(token.tokenURI(TOKENID_1)).toEqual(EMPTY_URI); + it('should return the empty string for an unset tokenURI', async () => { + expect(await token.tokenURI(TOKENID_1)).toEqual(EMPTY_URI); }); - it('should return the empty string if tokenURI set as default value', () => { - token._setTokenURI(TOKENID_1, EMPTY_URI); - expect(token.tokenURI(TOKENID_1)).toEqual(EMPTY_URI); + it('should return the empty string if tokenURI set as default value', async () => { + await token._setTokenURI(TOKENID_1, EMPTY_URI); + expect(await token.tokenURI(TOKENID_1)).toEqual(EMPTY_URI); }); - it('should return some string if tokenURI is set', () => { - token._setTokenURI(TOKENID_1, SOME_URI); - expect(token.tokenURI(TOKENID_1)).toEqual(SOME_URI); + it('should return some string if tokenURI is set', async () => { + await token._setTokenURI(TOKENID_1, SOME_URI); + expect(await token.tokenURI(TOKENID_1)).toEqual(SOME_URI); }); - it('should return very long tokenURI', () => { + it('should return very long tokenURI', async () => { const longURI = 'A'.repeat(1000); - token._setTokenURI(TOKENID_1, longURI); - expect(token.tokenURI(TOKENID_1)).toEqual(longURI); + await token._setTokenURI(TOKENID_1, longURI); + expect(await token.tokenURI(TOKENID_1)).toEqual(longURI); }); - it('should return tokenURI with special characters', () => { + it('should return tokenURI with special characters', async () => { const specialURI = '!@#$%^&*()_+'; - token._setTokenURI(TOKENID_1, specialURI); - expect(token.tokenURI(TOKENID_1)).toEqual(specialURI); + await token._setTokenURI(TOKENID_1, specialURI); + expect(await token.tokenURI(TOKENID_1)).toEqual(specialURI); }); - it('should update tokenURI multiple times', () => { - token._setTokenURI(TOKENID_1, 'URI1'); - token._setTokenURI(TOKENID_1, 'URI2'); - token._setTokenURI(TOKENID_1, 'URI3'); - expect(token.tokenURI(TOKENID_1)).toEqual('URI3'); + it('should update tokenURI multiple times', async () => { + await token._setTokenURI(TOKENID_1, 'URI1'); + await token._setTokenURI(TOKENID_1, 'URI2'); + await token._setTokenURI(TOKENID_1, 'URI3'); + expect(await token.tokenURI(TOKENID_1)).toEqual('URI3'); }); - it('should maintain tokenURI after token transfer', () => { - token._setTokenURI(TOKENID_1, SOME_URI); - token._transfer(OWNER.either, RECIPIENT.either, TOKENID_1); - expect(token.tokenURI(TOKENID_1)).toEqual(SOME_URI); + it('should maintain tokenURI after token transfer', async () => { + await token._setTokenURI(TOKENID_1, SOME_URI); + await token._transfer(OWNER.either, RECIPIENT.either, TOKENID_1); + expect(await token.tokenURI(TOKENID_1)).toEqual(SOME_URI); }); }); describe('approve', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + beforeEach(async () => { + await token._mint(OWNER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); - it('should throw if not owner', () => { - token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => { - token.approve(SPENDER.either, TOKENID_1); - }).toThrow('NonFungibleToken: invalid approver'); + it('should throw if not owner', async () => { + await token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect(token.approve(SPENDER.either, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: invalid approver', + ); }); - it('should approve spender', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(SPENDER.either); + it('should approve spender', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(SPENDER.either); }); - it('should allow operator to approve', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + it('should allow operator to approve', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.approve(OTHER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(OTHER.either); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.approve(OTHER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(OTHER.either); }); - it('spender approved for only TOKENID_1 should not be able to approve', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); + it('spender approved for only TOKENID_1 should not be able to approve', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); - token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.approve(OTHER.either, TOKENID_1); - }).toThrow('NonFungibleToken: invalid approver'); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await expect(token.approve(OTHER.either, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: invalid approver', + ); }); - it('should approve same address multiple times', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - token.approve(SPENDER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(SPENDER.either); + it('should approve same address multiple times', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + await token.approve(SPENDER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(SPENDER.either); }); - it('should approve after token transfer', () => { - token._transfer(OWNER.either, SPENDER.either, TOKENID_1); + it('should approve after token transfer', async () => { + await token._transfer(OWNER.either, SPENDER.either, TOKENID_1); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.approve(OTHER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(OTHER.either); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.approve(OTHER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(OTHER.either); }); - it('should approve after token burn and remint', () => { - token._burn(TOKENID_1); - token._mint(OWNER.either, TOKENID_1); + it('should approve after token burn and remint', async () => { + await token._burn(TOKENID_1); + await token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(SPENDER.either); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(SPENDER.either); }); - it('should approve with very long token ID', () => { + it('should approve with very long token ID', async () => { const longTokenId = BigInt('18446744073709551615'); - token._mint(OWNER.either, longTokenId); + await token._mint(OWNER.either, longTokenId); - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, longTokenId); - expect(token.getApproved(longTokenId)).toEqual(SPENDER.either); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, longTokenId); + expect(await token.getApproved(longTokenId)).toEqual(SPENDER.either); }); - it('should normalize right-variant zero approval', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(ZERO_CONTRACT, TOKENID_1); + it('should normalize right-variant zero approval', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(ZERO_CONTRACT, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); }); describe('getApproved', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKENID_1); + beforeEach(async () => { + await token._mint(OWNER.either, TOKENID_1); }); - it('should throw if token does not exist', () => { - expect(() => { - token.getApproved(NON_EXISTENT_TOKEN); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token does not exist', async () => { + await expect(token.getApproved(NON_EXISTENT_TOKEN)).rejects.toThrow( + 'NonFungibleToken: nonexistent token', + ); }); - it('should throw if token has been burned', () => { - token._burn(TOKENID_1); - expect(() => { - token.getApproved(TOKENID_1); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token has been burned', async () => { + await token._burn(TOKENID_1); + await expect(token.getApproved(TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: nonexistent token', + ); }); - it('should get current approved spender', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(OWNER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(OWNER.either); + it('should get current approved spender', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(OWNER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(OWNER.either); }); - it('should return zero if approval not set', () => { - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + it('should return zero if approval not set', async () => { + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); }); describe('setApprovalForAll', () => { - it('should not approve zero address', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); + it('should not approve zero address', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.setApprovalForAll(ZERO_ACCOUNT, true); - }).toThrow('NonFungibleToken: invalid operator'); + await expect(token.setApprovalForAll(ZERO_ACCOUNT, true)).rejects.toThrow( + 'NonFungibleToken: invalid operator', + ); }); - it('should set operator', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); + it('should set operator', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + await token.setApprovalForAll(SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); }); - it('should allow operator to manage owner tokens', () => { - token._mint(OWNER.either, TOKENID_1); - token._mint(OWNER.either, TOKENID_2); - token._mint(OWNER.either, TOKENID_3); + it('should allow operator to manage owner tokens', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._mint(OWNER.either, TOKENID_2); + await token._mint(OWNER.either, TOKENID_3); - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); - token.approve(OTHER.either, TOKENID_2); - expect(token.getApproved(TOKENID_2)).toEqual(OTHER.either); + await token.approve(OTHER.either, TOKENID_2); + expect(await token.getApproved(TOKENID_2)).toEqual(OTHER.either); - token.approve(SPENDER.either, TOKENID_3); - expect(token.getApproved(TOKENID_3)).toEqual(SPENDER.either); + await token.approve(SPENDER.either, TOKENID_3); + expect(await token.getApproved(TOKENID_3)).toEqual(SPENDER.either); }); - it('should revoke approval for all', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); + it('should revoke approval for all', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + await token.setApprovalForAll(SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); - token.setApprovalForAll(SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(false); + await token.setApprovalForAll(SPENDER.either, false); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + false, + ); - token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.approve(SPENDER.either, TOKENID_1); - }).toThrow('NonFungibleToken: invalid approver'); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await expect(token.approve(SPENDER.either, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: invalid approver', + ); }); - it('should set approval for all to same address multiple times', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); + it('should set approval for all to same address multiple times', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + await token.setApprovalForAll(SPENDER.either, true); + await token.setApprovalForAll(SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); }); - it('should set approval for all after token transfer', () => { - token._mint(OWNER.either, TOKENID_1); - token._transfer(OWNER.either, SPENDER.either, TOKENID_1); + it('should set approval for all after token transfer', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._transfer(OWNER.either, SPENDER.either, TOKENID_1); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.setApprovalForAll(OTHER.either, true); - expect(token.isApprovedForAll(SPENDER.either, OTHER.either)).toBe(true); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.setApprovalForAll(OTHER.either, true); + expect(await token.isApprovedForAll(SPENDER.either, OTHER.either)).toBe( + true, + ); }); - it('should set approval for all with multiple operators', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); + it('should set approval for all with multiple operators', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - token.setApprovalForAll(OTHER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); - expect(token.isApprovedForAll(OWNER.either, OTHER.either)).toBe(true); + await token.setApprovalForAll(SPENDER.either, true); + await token.setApprovalForAll(OTHER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); + expect(await token.isApprovedForAll(OWNER.either, OTHER.either)).toBe( + true, + ); }); - it('should set approval for all with very long token IDs', () => { + it('should set approval for all with very long token IDs', async () => { const longTokenId = BigInt('18446744073709551615'); - token._mint(OWNER.either, longTokenId); - token.privateState.injectSecretKey(OWNER.secretKey); + await token._mint(OWNER.either, longTokenId); + await token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + await token.setApprovalForAll(SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); }); }); describe('isApprovedForAll', () => { - it('should return false if approval not set', () => { - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(false); + it('should return false if approval not set', async () => { + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + false, + ); }); - it('should return true if approval set', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + it('should return true if approval set', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); }); - it('should return correct result with non-canonical owner lookup', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + it('should return correct result with non-canonical owner lookup', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); const nonCanonical = { is_left: true, @@ -500,13 +536,15 @@ describe('NonFungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - expect(token.isApprovedForAll(nonCanonical, SPENDER.either)).toBe(true); + expect(await token.isApprovedForAll(nonCanonical, SPENDER.either)).toBe( + true, + ); }); - it('should return correct result with non-canonical operator lookup', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + it('should return correct result with non-canonical operator lookup', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); const nonCanonical = { is_left: true, @@ -514,238 +552,240 @@ describe('NonFungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - expect(token.isApprovedForAll(OWNER.either, nonCanonical)).toBe(true); + expect(await token.isApprovedForAll(OWNER.either, nonCanonical)).toBe( + true, + ); }); }); describe('transferFrom', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKENID_1); + beforeEach(async () => { + await token._mint(OWNER.either, TOKENID_1); }); - it('should not transfer to ContractAddress', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, SOME_CONTRACT, TOKENID_1); - }).toThrow('NonFungibleToken: unsafe transfer'); + it('should not transfer to ContractAddress', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token.transferFrom(OWNER.either, SOME_CONTRACT, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: unsafe transfer'); }); - it('should not transfer to zero address', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, ZERO_ACCOUNT, TOKENID_1); - }).toThrow('NonFungibleToken: invalid receiver'); + it('should not transfer to zero address', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token.transferFrom(OWNER.either, ZERO_ACCOUNT, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: invalid receiver'); }); - it('should not transfer from zero address', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.transferFrom(ZERO_ACCOUNT, SPENDER.either, TOKENID_1); - }).toThrow('NonFungibleToken: incorrect owner'); + it('should not transfer from zero address', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token.transferFrom(ZERO_ACCOUNT, SPENDER.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: incorrect owner'); }); - it('should not transfer from unauthorized', () => { - token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); - expect(() => { - token.transferFrom(OWNER.either, UNAUTHORIZED.either, TOKENID_1); - }).toThrow('NonFungibleToken: insufficient approval'); + it('should not transfer from unauthorized', async () => { + await token.privateState.injectSecretKey(UNAUTHORIZED.secretKey); + await expect( + token.transferFrom(OWNER.either, UNAUTHORIZED.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: insufficient approval'); }); - it('should not transfer token that has not been minted', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, SPENDER.either, NON_EXISTENT_TOKEN); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should not transfer token that has not been minted', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token.transferFrom(OWNER.either, SPENDER.either, NON_EXISTENT_TOKEN), + ).rejects.toThrow('NonFungibleToken: nonexistent token'); }); - it('should transfer token without approvers or operators', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.transferFrom(OWNER.either, RECIPIENT.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(RECIPIENT.either); + it('should transfer token without approvers or operators', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.transferFrom(OWNER.either, RECIPIENT.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(RECIPIENT.either); }); - it('should transfer token via approved operator', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); + it('should transfer token via approved operator', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); }); - it('should transfer token via approvedForAll operator', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + it('should transfer token via approvedForAll operator', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); }); - it('should allow transfer to same address', () => { - token._approve(SPENDER.either, TOKENID_1, OWNER.either); - token._setApprovalForAll(OWNER.either, SPENDER.either, true); + it('should allow transfer to same address', async () => { + await token._approve(SPENDER.either, TOKENID_1, OWNER.either); + await token._setApprovalForAll(OWNER.either, SPENDER.either, true); - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, OWNER.either, TOKENID_1); - }).not.toThrow(); - expect(token.ownerOf(TOKENID_1)).toEqual(OWNER.either); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token.transferFrom(OWNER.either, OWNER.either, TOKENID_1), + ).resolves.not.toThrow(); + expect(await token.ownerOf(TOKENID_1)).toEqual(OWNER.either); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); expect( - token._isAuthorized(OWNER.either, SPENDER.either, TOKENID_1), + await token._isAuthorized(OWNER.either, SPENDER.either, TOKENID_1), ).toEqual(true); }); - it('should not transfer after approval revocation', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - token.approve(ZERO_ACCOUNT, TOKENID_1); + it('should not transfer after approval revocation', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + await token.approve(ZERO_ACCOUNT, TOKENID_1); - token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); - }).toThrow('NonFungibleToken: insufficient approval'); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await expect( + token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: insufficient approval'); }); - it('should not transfer after approval for all revocation', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - token.setApprovalForAll(SPENDER.either, false); + it('should not transfer after approval for all revocation', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); + await token.setApprovalForAll(SPENDER.either, false); - token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); - }).toThrow('NonFungibleToken: insufficient approval'); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await expect( + token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: insufficient approval'); }); - it('should transfer multiple tokens in sequence', () => { - token._mint(OWNER.either, TOKENID_2); - token._mint(OWNER.either, TOKENID_3); + it('should transfer multiple tokens in sequence', async () => { + await token._mint(OWNER.either, TOKENID_2); + await token._mint(OWNER.either, TOKENID_3); - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - token.approve(SPENDER.either, TOKENID_2); - token.approve(SPENDER.either, TOKENID_3); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + await token.approve(SPENDER.either, TOKENID_2); + await token.approve(SPENDER.either, TOKENID_3); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); - token.transferFrom(OWNER.either, SPENDER.either, TOKENID_2); - token.transferFrom(OWNER.either, SPENDER.either, TOKENID_3); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.transferFrom(OWNER.either, SPENDER.either, TOKENID_1); + await token.transferFrom(OWNER.either, SPENDER.either, TOKENID_2); + await token.transferFrom(OWNER.either, SPENDER.either, TOKENID_3); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); - expect(token.ownerOf(TOKENID_2)).toEqual(SPENDER.either); - expect(token.ownerOf(TOKENID_3)).toEqual(SPENDER.either); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + expect(await token.ownerOf(TOKENID_2)).toEqual(SPENDER.either); + expect(await token.ownerOf(TOKENID_3)).toEqual(SPENDER.either); }); - it('should transfer with very long token IDs', () => { + it('should transfer with very long token IDs', async () => { const longTokenId = BigInt('18446744073709551615'); - token._mint(OWNER.either, longTokenId); + await token._mint(OWNER.either, longTokenId); - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, longTokenId); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, longTokenId); - token.privateState.injectSecretKey(SPENDER.secretKey); - token.transferFrom(OWNER.either, SPENDER.either, longTokenId); - expect(token.ownerOf(longTokenId)).toEqual(SPENDER.either); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token.transferFrom(OWNER.either, SPENDER.either, longTokenId); + expect(await token.ownerOf(longTokenId)).toEqual(SPENDER.either); }); - it('should revoke approval after transferFrom', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - token._setApprovalForAll(OWNER.either, SPENDER.either, true); + it('should revoke approval after transferFrom', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + await token._setApprovalForAll(OWNER.either, SPENDER.either, true); - token.transferFrom(OWNER.either, OTHER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); - expect(token._isAuthorized(OTHER.either, SPENDER.either, TOKENID_1)).toBe( - false, - ); + await token.transferFrom(OWNER.either, OTHER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + expect( + await token._isAuthorized(OTHER.either, SPENDER.either, TOKENID_1), + ).toBe(false); - token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token.approve(UNAUTHORIZED.either, TOKENID_1); - }).toThrow('NonFungibleToken: invalid approver'); - expect(() => { - token.transferFrom(OTHER.either, UNAUTHORIZED.either, TOKENID_1); - }).toThrow('NonFungibleToken: insufficient approval'); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await expect( + token.approve(UNAUTHORIZED.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: invalid approver'); + await expect( + token.transferFrom(OTHER.either, UNAUTHORIZED.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: insufficient approval'); }); - it('should store canonical zero after clearing approval via transfer', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); + it('should store canonical zero after clearing approval via transfer', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); - token.transferFrom(OWNER.either, RECIPIENT.either, TOKENID_1); + await token.transferFrom(OWNER.either, RECIPIENT.either, TOKENID_1); // _update calls _approve(zeroAccount(), tokenId, zeroAccount()) internally, // which should store the left-variant zero - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); }); describe('_requireOwned', () => { - it('should throw if token has not been minted', () => { - expect(() => { - token._requireOwned(TOKENID_1); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token has not been minted', async () => { + await expect(token._requireOwned(TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: nonexistent token', + ); }); - it('should throw if token has been burned', () => { - token._mint(OWNER.either, TOKENID_1); - token._burn(TOKENID_1); - expect(() => { - token._requireOwned(TOKENID_1); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token has been burned', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._burn(TOKENID_1); + await expect(token._requireOwned(TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: nonexistent token', + ); }); - it('should return correct owner', () => { - token._mint(OWNER.either, TOKENID_1); - expect(token._requireOwned(TOKENID_1)).toEqual(OWNER.either); + it('should return correct owner', async () => { + await token._mint(OWNER.either, TOKENID_1); + expect(await token._requireOwned(TOKENID_1)).toEqual(OWNER.either); }); }); describe('_ownerOf', () => { - it('should return zero address if token does not exist', () => { - expect(token._ownerOf(NON_EXISTENT_TOKEN)).toEqual(ZERO_ACCOUNT); + it('should return zero address if token does not exist', async () => { + expect(await token._ownerOf(NON_EXISTENT_TOKEN)).toEqual(ZERO_ACCOUNT); }); - it('should return owner of token', () => { - token._mint(OWNER.either, TOKENID_1); - expect(token._ownerOf(TOKENID_1)).toEqual(OWNER.either); + it('should return owner of token', async () => { + await token._mint(OWNER.either, TOKENID_1); + expect(await token._ownerOf(TOKENID_1)).toEqual(OWNER.either); }); }); describe('_approve', () => { - it('should approve if auth is owner', () => { - token._mint(OWNER.either, TOKENID_1); - token._approve(SPENDER.either, TOKENID_1, OWNER.either); - expect(token.getApproved(TOKENID_1)).toEqual(SPENDER.either); + it('should approve if auth is owner', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._approve(SPENDER.either, TOKENID_1, OWNER.either); + expect(await token.getApproved(TOKENID_1)).toEqual(SPENDER.either); }); - it('should approve if auth is approved for all', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + it('should approve if auth is approved for all', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); - token._approve(SPENDER.either, TOKENID_1, SPENDER.either); - expect(token.getApproved(TOKENID_1)).toEqual(SPENDER.either); + await token._approve(SPENDER.either, TOKENID_1, SPENDER.either); + expect(await token.getApproved(TOKENID_1)).toEqual(SPENDER.either); }); - it('should throw if auth is unauthorized', () => { - token._mint(OWNER.either, TOKENID_1); - expect(() => { - token._approve(SPENDER.either, TOKENID_1, UNAUTHORIZED.either); - }).toThrow('NonFungibleToken: invalid approver'); + it('should throw if auth is unauthorized', async () => { + await token._mint(OWNER.either, TOKENID_1); + await expect( + token._approve(SPENDER.either, TOKENID_1, UNAUTHORIZED.either), + ).rejects.toThrow('NonFungibleToken: invalid approver'); }); - it('should approve if auth is zero address', () => { - token._mint(OWNER.either, TOKENID_1); - token._approve(SPENDER.either, TOKENID_1, ZERO_ACCOUNT); - expect(token.getApproved(TOKENID_1)).toEqual(SPENDER.either); + it('should approve if auth is zero address', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._approve(SPENDER.either, TOKENID_1, ZERO_ACCOUNT); + expect(await token.getApproved(TOKENID_1)).toEqual(SPENDER.either); }); - it('should canonicalize approved address', () => { - token._mint(OWNER.either, TOKENID_1); + it('should canonicalize approved address', async () => { + await token._mint(OWNER.either, TOKENID_1); const nonCanonical = { is_left: true, @@ -753,165 +793,173 @@ describe('NonFungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - token._approve(nonCanonical, TOKENID_1, OWNER.either); - expect(token.getApproved(TOKENID_1)).toEqual(SPENDER.either); + await token._approve(nonCanonical, TOKENID_1, OWNER.either); + expect(await token.getApproved(TOKENID_1)).toEqual(SPENDER.either); }); - it('should normalize right-variant zero to zeroAccount()', () => { - token._mint(OWNER.either, TOKENID_1); + it('should normalize right-variant zero to zeroAccount()', async () => { + await token._mint(OWNER.either, TOKENID_1); // Approve with a right-variant zero (contract address zero) - token._approve(ZERO_CONTRACT, TOKENID_1, OWNER.either); + await token._approve(ZERO_CONTRACT, TOKENID_1, OWNER.either); // getApproved should return the left-variant zeroAccount, not the right-variant - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); - it('should normalize left-variant zero to zeroAccount()', () => { - token._mint(OWNER.either, TOKENID_1); + it('should normalize left-variant zero to zeroAccount()', async () => { + await token._mint(OWNER.either, TOKENID_1); // First set a real approval - token._approve(SPENDER.either, TOKENID_1, OWNER.either); - expect(token.getApproved(TOKENID_1)).toEqual(SPENDER.either); + await token._approve(SPENDER.either, TOKENID_1, OWNER.either); + expect(await token.getApproved(TOKENID_1)).toEqual(SPENDER.either); // Clear it with left-variant zero - token._approve(ZERO_ACCOUNT, TOKENID_1, OWNER.either); - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + await token._approve(ZERO_ACCOUNT, TOKENID_1, OWNER.either); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); }); describe('_checkAuthorized', () => { - it('should throw if token not minted', () => { - expect(() => { - token._checkAuthorized(ZERO_ACCOUNT, OWNER.either, TOKENID_1); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token not minted', async () => { + await expect( + token._checkAuthorized(ZERO_ACCOUNT, OWNER.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: nonexistent token'); }); - it('should throw if unauthorized', () => { - token._mint(OWNER.either, TOKENID_1); - expect(() => { - token._checkAuthorized(OWNER.either, UNAUTHORIZED.either, TOKENID_1); - }).toThrow('NonFungibleToken: insufficient approval'); + it('should throw if unauthorized', async () => { + await token._mint(OWNER.either, TOKENID_1); + await expect( + token._checkAuthorized(OWNER.either, UNAUTHORIZED.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: insufficient approval'); }); - it('should not throw if approved', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - token._checkAuthorized(OWNER.either, SPENDER.either, TOKENID_1); + it('should not throw if approved', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + await token._checkAuthorized(OWNER.either, SPENDER.either, TOKENID_1); }); - it('should not throw if approvedForAll', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - token._checkAuthorized(OWNER.either, SPENDER.either, TOKENID_1); + it('should not throw if approvedForAll', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); + await token._checkAuthorized(OWNER.either, SPENDER.either, TOKENID_1); }); }); describe('_isAuthorized', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKENID_1); + beforeEach(async () => { + await token._mint(OWNER.either, TOKENID_1); }); - it('should return true if spender is authorized', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - expect(token._isAuthorized(OWNER.either, SPENDER.either, TOKENID_1)).toBe( - true, - ); + it('should return true if spender is authorized', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + expect( + await token._isAuthorized(OWNER.either, SPENDER.either, TOKENID_1), + ).toBe(true); }); - it('should return true if spender is authorized for all', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - expect(token._isAuthorized(OWNER.either, SPENDER.either, TOKENID_1)).toBe( - true, - ); + it('should return true if spender is authorized for all', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); + expect( + await token._isAuthorized(OWNER.either, SPENDER.either, TOKENID_1), + ).toBe(true); }); - it('should return true if spender is owner', () => { - expect(token._isAuthorized(OWNER.either, OWNER.either, TOKENID_1)).toBe( - true, - ); + it('should return true if spender is owner', async () => { + expect( + await token._isAuthorized(OWNER.either, OWNER.either, TOKENID_1), + ).toBe(true); }); - it('should return false if spender is zero address', () => { - expect(token._isAuthorized(OWNER.either, ZERO_ACCOUNT, TOKENID_1)).toBe( - false, - ); + it('should return false if spender is zero address', async () => { + expect( + await token._isAuthorized(OWNER.either, ZERO_ACCOUNT, TOKENID_1), + ).toBe(false); }); - it('should return false for unauthorized', () => { + it('should return false for unauthorized', async () => { expect( - token._isAuthorized(OWNER.either, UNAUTHORIZED.either, TOKENID_1), + await token._isAuthorized(OWNER.either, UNAUTHORIZED.either, TOKENID_1), ).toBe(false); }); }); describe('_getApproved', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKENID_1); + beforeEach(async () => { + await token._mint(OWNER.either, TOKENID_1); }); - it('should return zero address if token is not minted', () => { - expect(token._getApproved(NON_EXISTENT_TOKEN)).toEqual(ZERO_ACCOUNT); + it('should return zero address if token is not minted', async () => { + expect(await token._getApproved(NON_EXISTENT_TOKEN)).toEqual( + ZERO_ACCOUNT, + ); }); - it('should return approved address', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - expect(token._getApproved(TOKENID_1)).toEqual(SPENDER.either); + it('should return approved address', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + expect(await token._getApproved(TOKENID_1)).toEqual(SPENDER.either); }); - it('should return zero address if no approvals', () => { - expect(token._getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + it('should return zero address if no approvals', async () => { + expect(await token._getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); }); describe('_setApprovalForAll', () => { - it('should approve operator', () => { - token._mint(OWNER.either, TOKENID_1); - token._setApprovalForAll(OWNER.either, SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + it('should approve operator', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._setApprovalForAll(OWNER.either, SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); }); - it('should revoke operator approval', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + it('should revoke operator approval', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); - token._setApprovalForAll(OWNER.either, SPENDER.either, false); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(false); + await token._setApprovalForAll(OWNER.either, SPENDER.either, false); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + false, + ); }); - it('should throw if operator is zero address (left)', () => { - expect(() => { - token._setApprovalForAll(OWNER.either, ZERO_ACCOUNT, true); - }).toThrow('NonFungibleToken: invalid operator'); + it('should throw if operator is zero address (left)', async () => { + await expect( + token._setApprovalForAll(OWNER.either, ZERO_ACCOUNT, true), + ).rejects.toThrow('NonFungibleToken: invalid operator'); }); - it('should throw if operator is zero address (right)', () => { - expect(() => { - token._setApprovalForAll(OWNER.either, ZERO_CONTRACT, true); - }).toThrow('NonFungibleToken: invalid operator'); + it('should throw if operator is zero address (right)', async () => { + await expect( + token._setApprovalForAll(OWNER.either, ZERO_CONTRACT, true), + ).rejects.toThrow('NonFungibleToken: invalid operator'); }); - it('should fail if owner is zero address (left)', () => { - expect(() => { - token._setApprovalForAll(ZERO_ACCOUNT, RECIPIENT.either, true); - }).toThrow('NonFungibleToken: invalid owner'); + it('should fail if owner is zero address (left)', async () => { + await expect( + token._setApprovalForAll(ZERO_ACCOUNT, RECIPIENT.either, true), + ).rejects.toThrow('NonFungibleToken: invalid owner'); }); - it('should fail if owner is zero address (right)', () => { - expect(() => { - token._setApprovalForAll(ZERO_CONTRACT, RECIPIENT.either, true); - }).toThrow('NonFungibleToken: invalid owner'); + it('should fail if owner is zero address (right)', async () => { + await expect( + token._setApprovalForAll(ZERO_CONTRACT, RECIPIENT.either, true), + ).rejects.toThrow('NonFungibleToken: invalid owner'); }); - it('should canonicalize owner and operator', () => { - token._mint(OWNER.either, TOKENID_1); + it('should canonicalize owner and operator', async () => { + await token._mint(OWNER.either, TOKENID_1); const nonCanonicalOwner = { is_left: true, @@ -924,434 +972,436 @@ describe('NonFungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - token._setApprovalForAll(nonCanonicalOwner, nonCanonicalOp, true); - expect(token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe(true); + await token._setApprovalForAll(nonCanonicalOwner, nonCanonicalOp, true); + expect(await token.isApprovedForAll(OWNER.either, SPENDER.either)).toBe( + true, + ); }); }); describe('_mint', () => { - it('should not mint to ContractAddress', () => { - expect(() => { - token._mint(SOME_CONTRACT, TOKENID_1); - }).toThrow('NonFungibleToken: unsafe transfer'); + it('should not mint to ContractAddress', async () => { + await expect(token._mint(SOME_CONTRACT, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: unsafe transfer', + ); }); - it('should not mint to zero address', () => { - expect(() => { - token._mint(ZERO_ACCOUNT, TOKENID_1); - }).toThrow('NonFungibleToken: invalid receiver'); + it('should not mint to zero address', async () => { + await expect(token._mint(ZERO_ACCOUNT, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: invalid receiver', + ); }); - it('should not mint a token that already exists', () => { - token._mint(OWNER.either, TOKENID_1); - expect(() => { - token._mint(OWNER.either, TOKENID_1); - }).toThrow('NonFungibleToken: invalid sender'); + it('should not mint a token that already exists', async () => { + await token._mint(OWNER.either, TOKENID_1); + await expect(token._mint(OWNER.either, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: invalid sender', + ); }); - it('should mint token', () => { - token._mint(OWNER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(OWNER.either); - expect(token.balanceOf(OWNER.either)).toEqual(1n); + it('should mint token', async () => { + await token._mint(OWNER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(OWNER.either); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); - token._mint(OWNER.either, TOKENID_2); - token._mint(OWNER.either, TOKENID_3); - expect(token.balanceOf(OWNER.either)).toEqual(3n); + await token._mint(OWNER.either, TOKENID_2); + await token._mint(OWNER.either, TOKENID_3); + expect(await token.balanceOf(OWNER.either)).toEqual(3n); }); - it('should mint multiple tokens in sequence', () => { + it('should mint multiple tokens in sequence', async () => { for (let i = 0; i < 10; i++) { - token._mint(OWNER.either, TOKENID_1 + BigInt(i)); + await token._mint(OWNER.either, TOKENID_1 + BigInt(i)); } - expect(token.balanceOf(OWNER.either)).toEqual(10n); + expect(await token.balanceOf(OWNER.either)).toEqual(10n); }); - it('should mint with very long token IDs', () => { + it('should mint with very long token IDs', async () => { const longTokenId = BigInt('18446744073709551615'); - token._mint(OWNER.either, longTokenId); - expect(token.ownerOf(longTokenId)).toEqual(OWNER.either); + await token._mint(OWNER.either, longTokenId); + expect(await token.ownerOf(longTokenId)).toEqual(OWNER.either); }); - it('should mint after burning', () => { - token._mint(OWNER.either, TOKENID_1); - token._burn(TOKENID_1); - token._mint(OWNER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(OWNER.either); + it('should mint after burning', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._burn(TOKENID_1); + await token._mint(OWNER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(OWNER.either); }); - it('should mint with special characters in metadata', () => { - token._mint(OWNER.either, TOKENID_1); - token._setTokenURI(TOKENID_1, '!@#$%^&*()_+'); - expect(token.tokenURI(TOKENID_1)).toEqual('!@#$%^&*()_+'); + it('should mint with special characters in metadata', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._setTokenURI(TOKENID_1, '!@#$%^&*()_+'); + expect(await token.tokenURI(TOKENID_1)).toEqual('!@#$%^&*()_+'); }); - it('should canonicalize recipient', () => { + it('should canonicalize recipient', async () => { const nonCanonical = { is_left: true, left: OWNER.accountId, right: utils.encodeToAddress('JUNK_DATA'), }; - token._mint(nonCanonical, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(OWNER.either); - expect(token.balanceOf(OWNER.either)).toEqual(1n); + await token._mint(nonCanonical, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(OWNER.either); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); }); - it('should not mint to zero (contract)', () => { - expect(() => { - token._mint(ZERO_CONTRACT, TOKENID_1); - }).toThrow('NonFungibleToken: unsafe transfer'); + it('should not mint to zero (contract)', async () => { + await expect(token._mint(ZERO_CONTRACT, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: unsafe transfer', + ); }); }); describe('_burn', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKENID_1); + beforeEach(async () => { + await token._mint(OWNER.either, TOKENID_1); }); - it('should burn token', () => { - expect(token.balanceOf(OWNER.either)).toEqual(1n); + it('should burn token', async () => { + expect(await token.balanceOf(OWNER.either)).toEqual(1n); - token._burn(TOKENID_1); - expect(token._ownerOf(TOKENID_1)).toEqual(ZERO_ACCOUNT); - expect(token.balanceOf(OWNER.either)).toEqual(0n); + await token._burn(TOKENID_1); + expect(await token._ownerOf(TOKENID_1)).toEqual(ZERO_ACCOUNT); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); }); - it('should not burn a token that does not exist', () => { - expect(() => { - token._burn(NON_EXISTENT_TOKEN); - }).toThrow('NonFungibleToken: invalid sender'); + it('should not burn a token that does not exist', async () => { + await expect(token._burn(NON_EXISTENT_TOKEN)).rejects.toThrow( + 'NonFungibleToken: invalid sender', + ); }); - it('should clear approval when token is burned', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(SPENDER.either); + it('should clear approval when token is burned', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(SPENDER.either); - token._burn(TOKENID_1); - expect(token._getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + await token._burn(TOKENID_1); + expect(await token._getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); - it('should burn multiple tokens in sequence', () => { - token._mint(OWNER.either, TOKENID_2); - token._mint(OWNER.either, TOKENID_3); + it('should burn multiple tokens in sequence', async () => { + await token._mint(OWNER.either, TOKENID_2); + await token._mint(OWNER.either, TOKENID_3); - token._burn(TOKENID_1); - token._burn(TOKENID_2); - token._burn(TOKENID_3); - expect(token.balanceOf(OWNER.either)).toEqual(0n); + await token._burn(TOKENID_1); + await token._burn(TOKENID_2); + await token._burn(TOKENID_3); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); }); - it('should burn with very long token IDs', () => { + it('should burn with very long token IDs', async () => { const longTokenId = BigInt('18446744073709551615'); - token._mint(OWNER.either, longTokenId); - token._burn(longTokenId); - expect(token._ownerOf(longTokenId)).toEqual(ZERO_ACCOUNT); + await token._mint(OWNER.either, longTokenId); + await token._burn(longTokenId); + expect(await token._ownerOf(longTokenId)).toEqual(ZERO_ACCOUNT); }); - it('should burn after transfer', () => { - token._transfer(OWNER.either, SPENDER.either, TOKENID_1); - token._burn(TOKENID_1); - expect(token._ownerOf(TOKENID_1)).toEqual(ZERO_ACCOUNT); + it('should burn after transfer', async () => { + await token._transfer(OWNER.either, SPENDER.either, TOKENID_1); + await token._burn(TOKENID_1); + expect(await token._ownerOf(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); - it('should burn after approval', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - token._burn(TOKENID_1); - expect(token._ownerOf(TOKENID_1)).toEqual(ZERO_ACCOUNT); - expect(token._getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + it('should burn after approval', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + await token._burn(TOKENID_1); + expect(await token._ownerOf(TOKENID_1)).toEqual(ZERO_ACCOUNT); + expect(await token._getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); - it('should clear tokenURI on burn', () => { - token._setTokenURI(TOKENID_1, SOME_URI); - expect(token.tokenURI(TOKENID_1)).toEqual(SOME_URI); + it('should clear tokenURI on burn', async () => { + await token._setTokenURI(TOKENID_1, SOME_URI); + expect(await token.tokenURI(TOKENID_1)).toEqual(SOME_URI); - token._burn(TOKENID_1); + await token._burn(TOKENID_1); - token._mint(OWNER.either, TOKENID_1); - expect(token.tokenURI(TOKENID_1)).toEqual(EMPTY_URI); + await token._mint(OWNER.either, TOKENID_1); + expect(await token.tokenURI(TOKENID_1)).toEqual(EMPTY_URI); }); }); describe('_transfer', () => { - it('should not transfer to ContractAddress', () => { - token._mint(OWNER.either, TOKENID_1); - expect(() => { - token._transfer(OWNER.either, SOME_CONTRACT, TOKENID_1); - }).toThrow('NonFungibleToken: unsafe transfer'); + it('should not transfer to ContractAddress', async () => { + await token._mint(OWNER.either, TOKENID_1); + await expect( + token._transfer(OWNER.either, SOME_CONTRACT, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: unsafe transfer'); }); - it('should transfer token', () => { - token._mint(OWNER.either, TOKENID_1); - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(SPENDER.either)).toEqual(0n); - expect(token.ownerOf(TOKENID_1)).toEqual(OWNER.either); + it('should transfer token', async () => { + await token._mint(OWNER.either, TOKENID_1); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(SPENDER.either)).toEqual(0n); + expect(await token.ownerOf(TOKENID_1)).toEqual(OWNER.either); - token._transfer(OWNER.either, SPENDER.either, TOKENID_1); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(SPENDER.either)).toEqual(1n); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + await token._transfer(OWNER.either, SPENDER.either, TOKENID_1); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(SPENDER.either)).toEqual(1n); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); }); - it('should not transfer to zero address', () => { - token._mint(OWNER.either, TOKENID_1); - expect(() => { - token._transfer(OWNER.either, ZERO_ACCOUNT, TOKENID_1); - }).toThrow('NonFungibleToken: invalid receiver'); + it('should not transfer to zero address', async () => { + await token._mint(OWNER.either, TOKENID_1); + await expect( + token._transfer(OWNER.either, ZERO_ACCOUNT, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: invalid receiver'); }); - it('should throw if from does not own token', () => { - token._mint(OWNER.either, TOKENID_1); - expect(() => { - token._transfer(UNAUTHORIZED.either, SPENDER.either, TOKENID_1); - }).toThrow('NonFungibleToken: incorrect owner'); + it('should throw if from does not own token', async () => { + await token._mint(OWNER.either, TOKENID_1); + await expect( + token._transfer(UNAUTHORIZED.either, SPENDER.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: incorrect owner'); }); - it('should throw if token does not exist', () => { - expect(() => { - token._transfer(OWNER.either, SPENDER.either, NON_EXISTENT_TOKEN); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token does not exist', async () => { + await expect( + token._transfer(OWNER.either, SPENDER.either, NON_EXISTENT_TOKEN), + ).rejects.toThrow('NonFungibleToken: nonexistent token'); }); - it('should revoke approval after _transfer', () => { - token._mint(OWNER.either, TOKENID_1); - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - token._transfer(OWNER.either, OTHER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + it('should revoke approval after _transfer', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + await token._transfer(OWNER.either, OTHER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); }); describe('_setTokenURI', () => { - it('should throw if token does not exist', () => { - expect(() => { - token._setTokenURI(NON_EXISTENT_TOKEN, EMPTY_URI); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token does not exist', async () => { + await expect( + token._setTokenURI(NON_EXISTENT_TOKEN, EMPTY_URI), + ).rejects.toThrow('NonFungibleToken: nonexistent token'); }); - it('should set tokenURI', () => { - token._mint(OWNER.either, TOKENID_1); - token._setTokenURI(TOKENID_1, SOME_URI); - expect(token.tokenURI(TOKENID_1)).toEqual(SOME_URI); + it('should set tokenURI', async () => { + await token._mint(OWNER.either, TOKENID_1); + await token._setTokenURI(TOKENID_1, SOME_URI); + expect(await token.tokenURI(TOKENID_1)).toEqual(SOME_URI); }); }); describe('_unsafeMint', () => { - it('should mint to ContractAddress', () => { - expect(() => { - token._unsafeMint(SOME_CONTRACT, TOKENID_1); - }).not.toThrow(); + it('should mint to ContractAddress', async () => { + await expect( + token._unsafeMint(SOME_CONTRACT, TOKENID_1), + ).resolves.not.toThrow(); }); - it('should not mint to zero address (accountId)', () => { - expect(() => { - token._unsafeMint(ZERO_ACCOUNT, TOKENID_1); - }).toThrow('NonFungibleToken: invalid receiver'); + it('should not mint to zero address (accountId)', async () => { + await expect(token._unsafeMint(ZERO_ACCOUNT, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: invalid receiver', + ); }); - it('should not mint to zero address (contract)', () => { - expect(() => { - token._unsafeMint(ZERO_CONTRACT, TOKENID_1); - }).toThrow('NonFungibleToken: invalid receiver'); + it('should not mint to zero address (contract)', async () => { + await expect(token._unsafeMint(ZERO_CONTRACT, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: invalid receiver', + ); }); - it('should not mint a token that already exists', () => { - token._unsafeMint(OWNER.either, TOKENID_1); - expect(() => { - token._unsafeMint(OWNER.either, TOKENID_1); - }).toThrow('NonFungibleToken: invalid sender'); + it('should not mint a token that already exists', async () => { + await token._unsafeMint(OWNER.either, TOKENID_1); + await expect(token._unsafeMint(OWNER.either, TOKENID_1)).rejects.toThrow( + 'NonFungibleToken: invalid sender', + ); }); - it('should mint token to account', () => { - token._unsafeMint(OWNER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(OWNER.either); - expect(token.balanceOf(OWNER.either)).toEqual(1n); + it('should mint token to account', async () => { + await token._unsafeMint(OWNER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(OWNER.either); + expect(await token.balanceOf(OWNER.either)).toEqual(1n); - token._unsafeMint(OWNER.either, TOKENID_2); - token._unsafeMint(OWNER.either, TOKENID_3); - expect(token.balanceOf(OWNER.either)).toEqual(3n); + await token._unsafeMint(OWNER.either, TOKENID_2); + await token._unsafeMint(OWNER.either, TOKENID_3); + expect(await token.balanceOf(OWNER.either)).toEqual(3n); }); }); describe('_unsafeTransfer', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKENID_1); + beforeEach(async () => { + await token._mint(OWNER.either, TOKENID_1); }); - it('should transfer to ContractAddress', () => { - expect(() => { - token._unsafeTransfer(OWNER.either, SOME_CONTRACT, TOKENID_1); - }).not.toThrow(); + it('should transfer to ContractAddress', async () => { + await expect( + token._unsafeTransfer(OWNER.either, SOME_CONTRACT, TOKENID_1), + ).resolves.not.toThrow(); }); - it('should transfer token to account', () => { - expect(token.balanceOf(OWNER.either)).toEqual(1n); - expect(token.balanceOf(SPENDER.either)).toEqual(0n); - expect(token.ownerOf(TOKENID_1)).toEqual(OWNER.either); + it('should transfer token to account', async () => { + expect(await token.balanceOf(OWNER.either)).toEqual(1n); + expect(await token.balanceOf(SPENDER.either)).toEqual(0n); + expect(await token.ownerOf(TOKENID_1)).toEqual(OWNER.either); - token._unsafeTransfer(OWNER.either, SPENDER.either, TOKENID_1); - expect(token.balanceOf(OWNER.either)).toEqual(0n); - expect(token.balanceOf(SPENDER.either)).toEqual(1n); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + await token._unsafeTransfer(OWNER.either, SPENDER.either, TOKENID_1); + expect(await token.balanceOf(OWNER.either)).toEqual(0n); + expect(await token.balanceOf(SPENDER.either)).toEqual(1n); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); }); - it('should not transfer to zero address (accountId)', () => { - expect(() => { - token._unsafeTransfer(OWNER.either, ZERO_ACCOUNT, TOKENID_1); - }).toThrow('NonFungibleToken: invalid receiver'); + it('should not transfer to zero address (accountId)', async () => { + await expect( + token._unsafeTransfer(OWNER.either, ZERO_ACCOUNT, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: invalid receiver'); }); - it('should not transfer to zero address (contract)', () => { - expect(() => { - token._unsafeTransfer(OWNER.either, ZERO_CONTRACT, TOKENID_1); - }).toThrow('NonFungibleToken: invalid receiver'); + it('should not transfer to zero address (contract)', async () => { + await expect( + token._unsafeTransfer(OWNER.either, ZERO_CONTRACT, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: invalid receiver'); }); - it('should throw if from does not own token', () => { - expect(() => { + it('should throw if from does not own token', async () => { + await expect( token._unsafeTransfer( UNAUTHORIZED.either, UNAUTHORIZED.either, TOKENID_1, - ); - }).toThrow('NonFungibleToken: incorrect owner'); + ), + ).rejects.toThrow('NonFungibleToken: incorrect owner'); }); - it('should throw if token does not exist', () => { - expect(() => { - token._unsafeTransfer(OWNER.either, SPENDER.either, NON_EXISTENT_TOKEN); - }).toThrow('NonFungibleToken: nonexistent token'); + it('should throw if token does not exist', async () => { + await expect( + token._unsafeTransfer(OWNER.either, SPENDER.either, NON_EXISTENT_TOKEN), + ).rejects.toThrow('NonFungibleToken: nonexistent token'); }); - it('should revoke approval after _unsafeTransfer', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); - token._unsafeTransfer(OWNER.either, OTHER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + it('should revoke approval after _unsafeTransfer', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); + await token._unsafeTransfer(OWNER.either, OTHER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); - it('should canonicalize contract address recipient', () => { + it('should canonicalize contract address recipient', async () => { const nonCanonical = { is_left: false, left: new Uint8Array(32).fill(1), right: SOME_CONTRACT.right, }; - token._unsafeTransfer(OWNER.either, nonCanonical, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SOME_CONTRACT); - expect(token.balanceOf(SOME_CONTRACT)).toEqual(1n); + await token._unsafeTransfer(OWNER.either, nonCanonical, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SOME_CONTRACT); + expect(await token.balanceOf(SOME_CONTRACT)).toEqual(1n); }); - it('should handle non-canonical fromAddress', () => { + it('should handle non-canonical fromAddress', async () => { const nonCanonical = { is_left: true, left: OWNER.accountId, right: utils.encodeToAddress('JUNK_DATA'), }; - token._unsafeTransfer(nonCanonical, SPENDER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + await token._unsafeTransfer(nonCanonical, SPENDER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); }); }); describe('_unsafeTransferFrom', () => { - beforeEach(() => { - token._mint(OWNER.either, TOKENID_1); + beforeEach(async () => { + await token._mint(OWNER.either, TOKENID_1); }); - it('should transfer to ContractAddress', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token._unsafeTransferFrom(OWNER.either, SOME_CONTRACT, TOKENID_1); - }).not.toThrow(); + it('should transfer to ContractAddress', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token._unsafeTransferFrom(OWNER.either, SOME_CONTRACT, TOKENID_1), + ).resolves.not.toThrow(); }); - it('should not transfer to zero address (accountId)', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token._unsafeTransferFrom(OWNER.either, ZERO_ACCOUNT, TOKENID_1); - }).toThrow('NonFungibleToken: invalid receiver'); + it('should not transfer to zero address (accountId)', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token._unsafeTransferFrom(OWNER.either, ZERO_ACCOUNT, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: invalid receiver'); }); - it('should not transfer to zero address (contract)', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token._unsafeTransferFrom(OWNER.either, ZERO_CONTRACT, TOKENID_1); - }).toThrow('NonFungibleToken: invalid receiver'); + it('should not transfer to zero address (contract)', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token._unsafeTransferFrom(OWNER.either, ZERO_CONTRACT, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: invalid receiver'); }); - it('should not transfer from zero address', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { - token._unsafeTransferFrom(ZERO_ACCOUNT, SPENDER.either, TOKENID_1); - }).toThrow('NonFungibleToken: incorrect owner'); + it('should not transfer from zero address', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( + token._unsafeTransferFrom(ZERO_ACCOUNT, SPENDER.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: incorrect owner'); }); - it('unapproved operator should not transfer', () => { - token.privateState.injectSecretKey(SPENDER.secretKey); - expect(() => { - token._unsafeTransferFrom(OWNER.either, UNAUTHORIZED.either, TOKENID_1); - }).toThrow('NonFungibleToken: insufficient approval'); + it('unapproved operator should not transfer', async () => { + await token.privateState.injectSecretKey(SPENDER.secretKey); + await expect( + token._unsafeTransferFrom(OWNER.either, UNAUTHORIZED.either, TOKENID_1), + ).rejects.toThrow('NonFungibleToken: insufficient approval'); }); - it('should not transfer token that has not been minted', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - expect(() => { + it('should not transfer token that has not been minted', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await expect( token._unsafeTransferFrom( OWNER.either, SPENDER.either, NON_EXISTENT_TOKEN, - ); - }).toThrow('NonFungibleToken: nonexistent token'); + ), + ).rejects.toThrow('NonFungibleToken: nonexistent token'); }); - it('should transfer token to spender via approved operator', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); + it('should transfer token to spender via approved operator', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); - token.privateState.injectSecretKey(SPENDER.secretKey); - token._unsafeTransferFrom(OWNER.either, SPENDER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token._unsafeTransferFrom(OWNER.either, SPENDER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); }); - it('should transfer token to ContractAddress via approved operator', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); + it('should transfer token to ContractAddress via approved operator', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); - token.privateState.injectSecretKey(SPENDER.secretKey); - token._unsafeTransferFrom(OWNER.either, SOME_CONTRACT, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SOME_CONTRACT); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token._unsafeTransferFrom(OWNER.either, SOME_CONTRACT, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SOME_CONTRACT); }); - it('should transfer token to spender via approvedForAll operator', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + it('should transfer token to spender via approvedForAll operator', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); - token.privateState.injectSecretKey(SPENDER.secretKey); - token._unsafeTransferFrom(OWNER.either, SPENDER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token._unsafeTransferFrom(OWNER.either, SPENDER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); }); - it('should transfer token to ContractAddress via approvedForAll operator', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.setApprovalForAll(SPENDER.either, true); + it('should transfer token to ContractAddress via approvedForAll operator', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.setApprovalForAll(SPENDER.either, true); - token.privateState.injectSecretKey(SPENDER.secretKey); - token._unsafeTransferFrom(OWNER.either, SOME_CONTRACT, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SOME_CONTRACT); + await token.privateState.injectSecretKey(SPENDER.secretKey); + await token._unsafeTransferFrom(OWNER.either, SOME_CONTRACT, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SOME_CONTRACT); }); - it('should revoke approval after _unsafeTransferFrom', () => { - token.privateState.injectSecretKey(OWNER.secretKey); - token.approve(SPENDER.either, TOKENID_1); + it('should revoke approval after _unsafeTransferFrom', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); + await token.approve(SPENDER.either, TOKENID_1); - token._unsafeTransferFrom(OWNER.either, OTHER.either, TOKENID_1); - expect(token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); + await token._unsafeTransferFrom(OWNER.either, OTHER.either, TOKENID_1); + expect(await token.getApproved(TOKENID_1)).toEqual(ZERO_ACCOUNT); }); - it('should handle non-canonical fromAddress', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should handle non-canonical fromAddress', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); const nonCanonical = { is_left: true, @@ -1359,12 +1409,12 @@ describe('NonFungibleToken', () => { right: utils.encodeToAddress('JUNK_DATA'), }; - token._unsafeTransferFrom(nonCanonical, SPENDER.either, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); + await token._unsafeTransferFrom(nonCanonical, SPENDER.either, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SPENDER.either); }); - it('should canonicalize contract address recipient', () => { - token.privateState.injectSecretKey(OWNER.secretKey); + it('should canonicalize contract address recipient', async () => { + await token.privateState.injectSecretKey(OWNER.secretKey); const nonCanonical = { is_left: false, @@ -1372,9 +1422,9 @@ describe('NonFungibleToken', () => { right: SOME_CONTRACT.right, }; - token._unsafeTransferFrom(OWNER.either, nonCanonical, TOKENID_1); - expect(token.ownerOf(TOKENID_1)).toEqual(SOME_CONTRACT); - expect(token.balanceOf(SOME_CONTRACT)).toEqual(1n); + await token._unsafeTransferFrom(OWNER.either, nonCanonical, TOKENID_1); + expect(await token.ownerOf(TOKENID_1)).toEqual(SOME_CONTRACT); + expect(await token.balanceOf(SOME_CONTRACT)).toEqual(1n); }); }); }); @@ -1415,40 +1465,48 @@ const circuitsToFail: FailingCircuits[] = [ let uninitializedToken: NonFungibleTokenSimulator; describe('Uninitialized NonFungibleToken', () => { - beforeEach(() => { - uninitializedToken = new NonFungibleTokenSimulator(NAME, SYMBOL, BAD_INIT); + beforeEach(async () => { + uninitializedToken = await NonFungibleTokenSimulator.create( + NAME, + SYMBOL, + BAD_INIT, + ); }); - it.each(circuitsToFail)('%s should fail', (circuitName, args) => { - expect(() => { - (uninitializedToken[circuitName] as (...args: unknown[]) => unknown)( - ...args, - ); - }).toThrow('NonFungibleToken: contract not initialized'); + it.each(circuitsToFail)('%s should fail', async (circuitName, args) => { + await expect( + ( + uninitializedToken[circuitName] as ( + ...args: unknown[] + ) => Promise + )(...args), + ).rejects.toThrow('NonFungibleToken: contract not initialized'); }); }); describe('NonFungibleTokenSimulator wiring', () => { - it('should expose an empty public ledger via getPublicState', () => { - const sim = new NonFungibleTokenSimulator(NAME, SYMBOL, INIT); + it('should expose an empty public ledger via getPublicState', async () => { + const sim = await NonFungibleTokenSimulator.create(NAME, SYMBOL, INIT); - expect(sim.getPublicState()).toStrictEqual({}); + expect(await sim.getPublicState()).toStrictEqual({}); }); describe('privateState getCurrentSecretKey', () => { - it('should return the injected secret key', () => { - const sim = new NonFungibleTokenSimulator(NAME, SYMBOL, INIT); - sim.privateState.injectSecretKey(OWNER.secretKey); + it('should return the injected secret key', async () => { + const sim = await NonFungibleTokenSimulator.create(NAME, SYMBOL, INIT); + await sim.privateState.injectSecretKey(OWNER.secretKey); - expect(sim.privateState.getCurrentSecretKey()).toEqual(OWNER.secretKey); + expect(await sim.privateState.getCurrentSecretKey()).toEqual( + OWNER.secretKey, + ); }); - it('should throw when the secret key is undefined', () => { - const sim = new NonFungibleTokenSimulator(NAME, SYMBOL, INIT, { + it('should throw when the secret key is undefined', async () => { + const sim = await NonFungibleTokenSimulator.create(NAME, SYMBOL, INIT, { privateState: { secretKey: undefined as unknown as Uint8Array }, }); - expect(() => sim.privateState.getCurrentSecretKey()).toThrow( + await expect(sim.privateState.getCurrentSecretKey()).rejects.toThrow( 'Missing secret key', ); }); diff --git a/contracts/src/token/test/simulators/FungibleTokenSimulator.ts b/contracts/src/token/test/simulators/FungibleTokenSimulator.ts index 3a9ef8da..a6ba9190 100644 --- a/contracts/src/token/test/simulators/FungibleTokenSimulator.ts +++ b/contracts/src/token/test/simulators/FungibleTokenSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -41,29 +41,34 @@ const FungibleTokenSimulatorBase = createSimulator< ], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => FungibleTokenWitnesses(), + artifactName: 'MockFungibleToken', }); /** * FungibleToken Simulator */ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { - constructor( + static async create( name: string, symbol: string, decimals: bigint, init: boolean, - options: BaseSimulatorOptions< + options: SimulatorOptions< FungibleTokenPrivateState, ReturnType > = {}, - ) { - super([name, symbol, decimals, init], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [name, symbol, decimals, init], + options, + ) as Promise; } /** * @description Returns the token name. * @returns The token name. */ - public name(): string { + public name(): Promise { return this.circuits.impure.name(); } @@ -71,7 +76,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @description Returns the symbol of the token. * @returns The token name. */ - public symbol(): string { + public symbol(): Promise { return this.circuits.impure.symbol(); } @@ -79,7 +84,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @description Returns the number of decimals used to get its user representation. * @returns The account's token balance. */ - public decimals(): bigint { + public decimals(): Promise { return this.circuits.impure.decimals(); } @@ -87,7 +92,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @description Returns the value of tokens in existence. * @returns The total supply of tokens. */ - public totalSupply(): bigint { + public totalSupply(): Promise { return this.circuits.impure.totalSupply(); } @@ -96,7 +101,9 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param account The public key or contract address to query. * @returns The account's token balance. */ - public balanceOf(account: Either): bigint { + public balanceOf( + account: Either, + ): Promise { return this.circuits.impure.balanceOf(account); } @@ -110,7 +117,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { public allowance( owner: Either, spender: Either, - ): bigint { + ): Promise { return this.circuits.impure.allowance(owner, spender); } @@ -123,7 +130,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { public transfer( to: Either, value: bigint, - ): boolean { + ): Promise { return this.circuits.impure.transfer(to, value); } @@ -136,7 +143,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { public _unsafeTransfer( to: Either, value: bigint, - ): boolean { + ): Promise { return this.circuits.impure._unsafeTransfer(to, value); } @@ -152,7 +159,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { fromAddress: Either, to: Either, value: bigint, - ): boolean { + ): Promise { return this.circuits.impure.transferFrom(fromAddress, to, value); } @@ -167,7 +174,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { fromAddress: Either, to: Either, value: bigint, - ): boolean { + ): Promise { return this.circuits.impure._unsafeTransferFrom(fromAddress, to, value); } @@ -180,7 +187,7 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { public approve( spender: Either, value: bigint, - ): boolean { + ): Promise { return this.circuits.impure.approve(spender, value); } @@ -200,8 +207,8 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { fromAddress: Either, to: Either, value: bigint, - ) { - this.circuits.impure._transfer(fromAddress, to, value); + ): Promise<[]> { + return this.circuits.impure._transfer(fromAddress, to, value); } /** @@ -214,8 +221,12 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { fromAddress: Either, to: Either, value: bigint, - ) { - this.circuits.impure._unsafeUncheckedTransfer(fromAddress, to, value); + ): Promise<[]> { + return this.circuits.impure._unsafeUncheckedTransfer( + fromAddress, + to, + value, + ); } /** @@ -224,8 +235,11 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param account The recipient of tokens minted. * @param value The amount of tokens minted. */ - public _mint(account: Either, value: bigint) { - this.circuits.impure._mint(account, value); + public _mint( + account: Either, + value: bigint, + ): Promise<[]> { + return this.circuits.impure._mint(account, value); } /** @@ -236,8 +250,8 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { public _unsafeMint( account: Either, value: bigint, - ) { - this.circuits.impure._unsafeMint(account, value); + ): Promise<[]> { + return this.circuits.impure._unsafeMint(account, value); } /** @@ -246,8 +260,11 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param account The target owner of tokens to burn. * @param value The amount of tokens to burn. */ - public _burn(account: Either, value: bigint) { - this.circuits.impure._burn(account, value); + public _burn( + account: Either, + value: bigint, + ): Promise<[]> { + return this.circuits.impure._burn(account, value); } /** @@ -262,8 +279,8 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { owner: Either, spender: Either, value: bigint, - ) { - this.circuits.impure._approve(owner, spender, value); + ): Promise<[]> { + return this.circuits.impure._approve(owner, spender, value); } /** @@ -277,8 +294,8 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { owner: Either, spender: Either, value: bigint, - ) { - this.circuits.impure._spendAllowance(owner, spender, value); + ): Promise<[]> { + return this.circuits.impure._spendAllowance(owner, spender, value); } public readonly privateState = { @@ -289,9 +306,11 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @param newSK - The new secret key to set. * @returns The updated private state. */ - injectSecretKey: (newSK: Uint8Array): FungibleTokenPrivateState => { + injectSecretKey: async ( + newSK: Uint8Array, + ): Promise => { const updatedState = FungibleTokenPrivateState.withSecretKey(newSK); - this.circuitContextManager.updatePrivateState(updatedState); + this.setPrivateState(updatedState); return updatedState; }, @@ -300,8 +319,8 @@ export class FungibleTokenSimulator extends FungibleTokenSimulatorBase { * @returns The secret key. * @throws If the secret key is undefined. */ - getCurrentSecretKey: (): Uint8Array => { - const sk = this.getPrivateState().secretKey; + getCurrentSecretKey: async (): Promise => { + const sk = (await this.getPrivateState()).secretKey; if (typeof sk === 'undefined') { throw new Error('Missing secret key'); } diff --git a/contracts/src/token/test/simulators/MultiTokenSimulator.ts b/contracts/src/token/test/simulators/MultiTokenSimulator.ts index 388ddf88..2702a3bb 100644 --- a/contracts/src/token/test/simulators/MultiTokenSimulator.ts +++ b/contracts/src/token/test/simulators/MultiTokenSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -32,20 +32,22 @@ const MultiTokenSimulatorBase = createSimulator< contractArgs: (_uri) => [_uri], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => MultiTokenWitnesses(), + artifactName: 'MockMultiToken', }); /** * MultiToken Simulator */ export class MultiTokenSimulator extends MultiTokenSimulatorBase { - constructor( + static async create( _uri: Maybe, - options: BaseSimulatorOptions< + options: SimulatorOptions< MultiTokenPrivateState, ReturnType > = {}, - ) { - super([_uri], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create([_uri], options) as Promise; } /** @@ -53,8 +55,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { * however, this method enables the tests to assert it cannot be called again. * @param uri The base URI for all token URIs. */ - public initialize(uri: string) { - this.circuits.impure.initialize(uri); + public initialize(uri: string): Promise<[]> { + return this.circuits.impure.initialize(uri); } /** @@ -62,7 +64,7 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { * @param id The token identifier to query. * @returns The token URI. */ - public uri(id: bigint): string { + public uri(id: bigint): Promise { return this.circuits.impure.uri(id); } @@ -75,7 +77,7 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { public balanceOf( account: Either, id: bigint, - ): bigint { + ): Promise { return this.circuits.impure.balanceOf(account, id); } @@ -88,8 +90,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { public setApprovalForAll( operator: Either, approved: boolean, - ) { - this.circuits.impure.setApprovalForAll(operator, approved); + ): Promise<[]> { + return this.circuits.impure.setApprovalForAll(operator, approved); } /** @@ -101,7 +103,7 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { public isApprovedForAll( account: Either, operator: Either, - ): boolean { + ): Promise { return this.circuits.impure.isApprovedForAll(account, operator); } @@ -118,8 +120,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { to: Either, id: bigint, value: bigint, - ) { - this.circuits.impure.transferFrom(fromAddress, to, id, value); + ): Promise<[]> { + return this.circuits.impure.transferFrom(fromAddress, to, id, value); } /** @@ -135,8 +137,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { to: Either, id: bigint, value: bigint, - ) { - this.circuits.impure._unsafeTransferFrom(fromAddress, to, id, value); + ): Promise<[]> { + return this.circuits.impure._unsafeTransferFrom(fromAddress, to, id, value); } /** @@ -153,8 +155,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { to: Either, id: bigint, value: bigint, - ) { - this.circuits.impure._transfer(fromAddress, to, id, value); + ): Promise<[]> { + return this.circuits.impure._transfer(fromAddress, to, id, value); } /** @@ -171,16 +173,16 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { to: Either, id: bigint, value: bigint, - ) { - this.circuits.impure._unsafeTransfer(fromAddress, to, id, value); + ): Promise<[]> { + return this.circuits.impure._unsafeTransfer(fromAddress, to, id, value); } /** * @description Sets a new URI for all token types. * @param newURI The new base URI for all tokens. */ - public _setURI(newURI: string) { - this.circuits.impure._setURI(newURI); + public _setURI(newURI: string): Promise<[]> { + return this.circuits.impure._setURI(newURI); } /** @@ -193,8 +195,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { to: Either, id: bigint, value: bigint, - ) { - this.circuits.impure._mint(to, id, value); + ): Promise<[]> { + return this.circuits.impure._mint(to, id, value); } /** @@ -207,8 +209,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { to: Either, id: bigint, value: bigint, - ) { - this.circuits.impure._unsafeMint(to, id, value); + ): Promise<[]> { + return this.circuits.impure._unsafeMint(to, id, value); } /** @@ -221,8 +223,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { fromAddress: Either, id: bigint, value: bigint, - ) { - this.circuits.impure._burn(fromAddress, id, value); + ): Promise<[]> { + return this.circuits.impure._burn(fromAddress, id, value); } /** @@ -237,8 +239,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { owner: Either, operator: Either, approved: boolean, - ) { - this.circuits.impure._setApprovalForAll(owner, operator, approved); + ): Promise<[]> { + return this.circuits.impure._setApprovalForAll(owner, operator, approved); } public readonly privateState = { @@ -249,9 +251,11 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { * @param newSK - The new secret key to set. * @returns The updated private state. */ - injectSecretKey: (newSK: Uint8Array): MultiTokenPrivateState => { + injectSecretKey: async ( + newSK: Uint8Array, + ): Promise => { const updatedState = MultiTokenPrivateState.withSecretKey(newSK); - this.circuitContextManager.updatePrivateState(updatedState); + this.setPrivateState(updatedState); return updatedState; }, @@ -260,8 +264,8 @@ export class MultiTokenSimulator extends MultiTokenSimulatorBase { * @returns The secret key. * @throws If the secret key is undefined. */ - getCurrentSecretKey: (): Uint8Array => { - const sk = this.getPrivateState().secretKey; + getCurrentSecretKey: async (): Promise => { + const sk = (await this.getPrivateState()).secretKey; if (typeof sk === 'undefined') { throw new Error('Missing secret key'); } diff --git a/contracts/src/token/test/simulators/NonFungibleTokenSimulator.ts b/contracts/src/token/test/simulators/NonFungibleTokenSimulator.ts index 02ce0d92..a2622111 100644 --- a/contracts/src/token/test/simulators/NonFungibleTokenSimulator.ts +++ b/contracts/src/token/test/simulators/NonFungibleTokenSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -35,29 +35,34 @@ const NonFungibleTokenSimulatorBase = createSimulator< contractArgs: (name, symbol, init) => [name, symbol, init], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => NonFungibleTokenWitnesses(), + artifactName: 'MockNonFungibleToken', }); /** * NonFungibleToken Simulator */ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { - constructor( + static async create( name: string, symbol: string, init: boolean, - options: BaseSimulatorOptions< + options: SimulatorOptions< NonFungibleTokenPrivateState, ReturnType > = {}, - ) { - super([name, symbol, init], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create( + [name, symbol, init], + options, + ) as Promise; } /** * @description Returns the token name. * @returns The token name. */ - public name(): string { + public name(): Promise { return this.circuits.impure.name(); } @@ -65,7 +70,7 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @description Returns the symbol of the token. * @returns The token symbol. */ - public symbol(): string { + public symbol(): Promise { return this.circuits.impure.symbol(); } @@ -74,7 +79,9 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param account The public key to query. * @return The number of tokens in `account`'s account. */ - public balanceOf(account: Either): bigint { + public balanceOf( + account: Either, + ): Promise { return this.circuits.impure.balanceOf(account); } @@ -83,7 +90,9 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param tokenId The identifier for a token. * @return The public key that owns the token. */ - public ownerOf(tokenId: bigint): Either { + public ownerOf( + tokenId: bigint, + ): Promise> { return this.circuits.impure.ownerOf(tokenId); } @@ -97,7 +106,7 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param tokenId The identifier for a token. * @returns The token id's URI. */ - public tokenURI(tokenId: bigint): string { + public tokenURI(tokenId: bigint): Promise { return this.circuits.impure.tokenURI(tokenId); } @@ -115,8 +124,11 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param to The account receiving the approval * @param tokenId The token `to` may be permitted to transfer */ - public approve(to: Either, tokenId: bigint) { - this.circuits.impure.approve(to, tokenId); + public approve( + to: Either, + tokenId: bigint, + ): Promise<[]> { + return this.circuits.impure.approve(to, tokenId); } /** @@ -124,7 +136,9 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param tokenId The token an account may be approved to manage * @return The account approved to manage the token */ - public getApproved(tokenId: bigint): Either { + public getApproved( + tokenId: bigint, + ): Promise> { return this.circuits.impure.getApproved(tokenId); } @@ -142,8 +156,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { public setApprovalForAll( operator: Either, approved: boolean, - ) { - this.circuits.impure.setApprovalForAll(operator, approved); + ): Promise<[]> { + return this.circuits.impure.setApprovalForAll(operator, approved); } /** @@ -156,7 +170,7 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { public isApprovedForAll( owner: Either, operator: Either, - ): boolean { + ): Promise { return this.circuits.impure.isApprovedForAll(owner, operator); } @@ -178,8 +192,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { fromAddress: Either, to: Either, tokenId: bigint, - ) { - this.circuits.impure.transferFrom(fromAddress, to, tokenId); + ): Promise<[]> { + return this.circuits.impure.transferFrom(fromAddress, to, tokenId); } /** @@ -191,7 +205,9 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param tokenId The token that should be owned * @return The owner of `tokenId` */ - public _requireOwned(tokenId: bigint): Either { + public _requireOwned( + tokenId: bigint, + ): Promise> { return this.circuits.impure._requireOwned(tokenId); } @@ -201,7 +217,9 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param tokenId The target token of the owner query * @return The owner of the token */ - public _ownerOf(tokenId: bigint): Either { + public _ownerOf( + tokenId: bigint, + ): Promise> { return this.circuits.impure._ownerOf(tokenId); } @@ -219,8 +237,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { to: Either, tokenId: bigint, auth: Either, - ) { - this.circuits.impure._approve(to, tokenId, auth); + ): Promise<[]> { + return this.circuits.impure._approve(to, tokenId, auth); } /** @@ -240,7 +258,7 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { owner: Either, spender: Either, tokenId: bigint, - ) { + ): Promise<[]> { return this.circuits.impure._checkAuthorized(owner, spender, tokenId); } @@ -260,7 +278,7 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { owner: Either, spender: Either, tokenId: bigint, - ): boolean { + ): Promise { return this.circuits.impure._isAuthorized(owner, spender, tokenId); } @@ -270,7 +288,9 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param tokenId The token to query * @return An account approved to spend `tokenId` */ - public _getApproved(tokenId: bigint): Either { + public _getApproved( + tokenId: bigint, + ): Promise> { return this.circuits.impure._getApproved(tokenId); } @@ -289,8 +309,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { owner: Either, operator: Either, approved: boolean, - ) { - this.circuits.impure._setApprovalForAll(owner, operator, approved); + ): Promise<[]> { + return this.circuits.impure._setApprovalForAll(owner, operator, approved); } /** @@ -304,8 +324,11 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param to The account receiving `tokenId` * @param tokenId The token to transfer */ - public _mint(to: Either, tokenId: bigint) { - this.circuits.impure._mint(to, tokenId); + public _mint( + to: Either, + tokenId: bigint, + ): Promise<[]> { + return this.circuits.impure._mint(to, tokenId); } /** @@ -319,8 +342,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * * @param tokenId The token to burn */ - public _burn(tokenId: bigint) { - this.circuits.impure._burn(tokenId); + public _burn(tokenId: bigint): Promise<[]> { + return this.circuits.impure._burn(tokenId); } /** @@ -340,8 +363,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { fromAddress: Either, to: Either, tokenId: bigint, - ) { - this.circuits.impure._transfer(fromAddress, to, tokenId); + ): Promise<[]> { + return this.circuits.impure._transfer(fromAddress, to, tokenId); } /** @@ -353,8 +376,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param tokenId The identifier of the token. * @param tokenURI The URI of `tokenId`. */ - public _setTokenURI(tokenId: bigint, tokenURI: string) { - this.circuits.impure._setTokenURI(tokenId, tokenURI); + public _setTokenURI(tokenId: bigint, tokenURI: string): Promise<[]> { + return this.circuits.impure._setTokenURI(tokenId, tokenURI); } /** @@ -379,8 +402,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { fromAddress: Either, to: Either, tokenId: bigint, - ) { - this.circuits.impure._unsafeTransferFrom(fromAddress, to, tokenId); + ): Promise<[]> { + return this.circuits.impure._unsafeTransferFrom(fromAddress, to, tokenId); } /** @@ -405,8 +428,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { fromAddress: Either, to: Either, tokenId: bigint, - ) { - this.circuits.impure._unsafeTransfer(fromAddress, to, tokenId); + ): Promise<[]> { + return this.circuits.impure._unsafeTransfer(fromAddress, to, tokenId); } /** @@ -424,8 +447,11 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param {Either} to - The account receiving `tokenId` * @param {TokenId} tokenId - The token to transfer */ - public _unsafeMint(to: Either, tokenId: bigint) { - this.circuits.impure._unsafeMint(to, tokenId); + public _unsafeMint( + to: Either, + tokenId: bigint, + ): Promise<[]> { + return this.circuits.impure._unsafeMint(to, tokenId); } public readonly privateState = { @@ -436,9 +462,11 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @param newSK - The new secret key to set. * @returns The updated private state. */ - injectSecretKey: (newSK: Uint8Array): NonFungibleTokenPrivateState => { + injectSecretKey: async ( + newSK: Uint8Array, + ): Promise => { const updatedState = NonFungibleTokenPrivateState.withSecretKey(newSK); - this.circuitContextManager.updatePrivateState(updatedState); + this.setPrivateState(updatedState); return updatedState; }, @@ -447,8 +475,8 @@ export class NonFungibleTokenSimulator extends NonFungibleTokenSimulatorBase { * @returns The secret key. * @throws If the secret key is undefined. */ - getCurrentSecretKey: (): Uint8Array => { - const sk = this.getPrivateState().secretKey; + getCurrentSecretKey: async (): Promise => { + const sk = (await this.getPrivateState()).secretKey; if (typeof sk === 'undefined') { throw new Error('Missing secret key'); } diff --git a/contracts/src/utils/test/simulators/UtilsSimulator.ts b/contracts/src/utils/test/simulators/UtilsSimulator.ts index 9832db76..67a0812b 100644 --- a/contracts/src/utils/test/simulators/UtilsSimulator.ts +++ b/contracts/src/utils/test/simulators/UtilsSimulator.ts @@ -1,6 +1,6 @@ import { - type BaseSimulatorOptions, createSimulator, + type SimulatorOptions, } from '@openzeppelin/compact-simulator'; import { type ContractAddress, @@ -31,19 +31,21 @@ const UtilsSimulatorBase = createSimulator< contractArgs: () => [], ledgerExtractor: (state) => ledger(state), witnessesFactory: () => UtilsWitnesses(), + artifactName: 'MockUtils', }); /** * Utils Simulator */ export class UtilsSimulator extends UtilsSimulatorBase { - constructor( - options: BaseSimulatorOptions< + static async create( + options: SimulatorOptions< UtilsPrivateState, ReturnType > = {}, - ) { - super([], options); + ): Promise { + // biome-ignore lint/complexity/noThisInStatic: super.create must keep the subclass `this` + return super.create([], options) as Promise; } /** @@ -53,7 +55,7 @@ export class UtilsSimulator extends UtilsSimulatorBase { */ public isKeyOrAddressZero( keyOrAddress: Either, - ): boolean { + ): Promise { return this.circuits.pure.isKeyOrAddressZero(keyOrAddress); } @@ -69,7 +71,7 @@ export class UtilsSimulator extends UtilsSimulatorBase { public isKeyOrAddressEqual( keyOrAddress: Either, other: Either, - ): boolean { + ): Promise { return this.circuits.pure.isKeyOrAddressEqual(keyOrAddress, other); } @@ -78,7 +80,7 @@ export class UtilsSimulator extends UtilsSimulatorBase { * @param key The target value to check. * @returns Returns true if `key` is zero. */ - public isKeyZero(key: ZswapCoinPublicKey): boolean { + public isKeyZero(key: ZswapCoinPublicKey): Promise { return this.circuits.pure.isKeyZero(key); } @@ -89,7 +91,7 @@ export class UtilsSimulator extends UtilsSimulatorBase { */ public isContractAddress( keyOrAddress: Either, - ): boolean { + ): Promise { return this.circuits.pure.isContractAddress(keyOrAddress); } @@ -97,7 +99,7 @@ export class UtilsSimulator extends UtilsSimulatorBase { * @description A helper function that returns the empty string: "" * @returns The empty string: "" */ - public emptyString(): string { + public emptyString(): Promise { return this.circuits.pure.emptyString(); } @@ -108,7 +110,7 @@ export class UtilsSimulator extends UtilsSimulatorBase { */ public canonicalizeKeyOrAddress( keyOrAddress: Either, - ): Either { + ): Promise> { return this.circuits.pure.canonicalizeKeyOrAddress(keyOrAddress); } @@ -117,7 +119,9 @@ export class UtilsSimulator extends UtilsSimulatorBase { * right-variant `Either`. * @returns The contract's own address as a recipient. */ - public selfAsRecipient(): Either { + public selfAsRecipient(): Promise< + Either + > { return this.circuits.impure.selfAsRecipient(); } @@ -125,7 +129,7 @@ export class UtilsSimulator extends UtilsSimulatorBase { * @description The maximum value representable by a `Uint<128>`. * @returns `2^128 - 1`. */ - public UINT128_MAX(): bigint { + public UINT128_MAX(): Promise { return this.circuits.pure.UINT128_MAX(); } @@ -134,7 +138,7 @@ export class UtilsSimulator extends UtilsSimulatorBase { * (left variant with zero `Bytes<32>`). * @returns The canonical zero value. */ - public zeroAccount(): Either { + public zeroAccount(): Promise> { return this.circuits.pure.zeroAccount(); } @@ -143,7 +147,9 @@ export class UtilsSimulator extends UtilsSimulatorBase { * @param target The value to check. * @returns Returns true if the active branch is zero. */ - public isTargetZero(target: Either): boolean { + public isTargetZero( + target: Either, + ): Promise { return this.circuits.pure.isTargetZero(target); } @@ -153,7 +159,7 @@ export class UtilsSimulator extends UtilsSimulatorBase { * @param secretKey A 32-byte cryptographically secure random value. * @returns The computed account identifier. */ - public computeAccountId(secretKey: Uint8Array): Uint8Array { + public computeAccountId(secretKey: Uint8Array): Promise { return this.circuits.pure.computeAccountId(secretKey); } } diff --git a/contracts/src/utils/test/utils.test.ts b/contracts/src/utils/test/utils.test.ts index 12f6d211..ed2db809 100644 --- a/contracts/src/utils/test/utils.test.ts +++ b/contracts/src/utils/test/utils.test.ts @@ -3,7 +3,7 @@ import { CompactTypeVector, persistentHash, } from '@midnight-ntwrk/compact-runtime'; -import { describe, expect, it } from 'vitest'; +import { beforeEach, describe, expect, it } from 'vitest'; import * as contractUtils from '#test-utils/address.js'; import { UtilsSimulator } from './simulators/UtilsSimulator.js'; @@ -45,55 +45,63 @@ const eitherContract = (str: string) => ({ let contract: UtilsSimulator; describe('Utils', () => { - contract = new UtilsSimulator(); + beforeEach(async () => { + contract = await UtilsSimulator.create(); + }); describe('isKeyOrAddressZero', () => { - it('should return zero for the zero address', () => { - expect(contract.isKeyOrAddressZero(contractUtils.ZERO_KEY)).toBe(true); + it('should return zero for the zero address', async () => { + expect(await contract.isKeyOrAddressZero(contractUtils.ZERO_KEY)).toBe( + true, + ); }); - it('should not return zero for nonzero addresses', () => { - expect(contract.isKeyOrAddressZero(Z_SOME_KEY)).toBe(false); - expect(contract.isKeyOrAddressZero(SOME_CONTRACT)).toBe(false); + it('should not return zero for nonzero addresses', async () => { + expect(await contract.isKeyOrAddressZero(Z_SOME_KEY)).toBe(false); + expect(await contract.isKeyOrAddressZero(SOME_CONTRACT)).toBe(false); }); - it('should not return zero for a zero contract address', () => { - expect(contract.isKeyOrAddressZero(contractUtils.ZERO_ADDRESS)).toBe( - true, - ); + it('should not return zero for a zero contract address', async () => { + expect( + await contract.isKeyOrAddressZero(contractUtils.ZERO_ADDRESS), + ).toBe(true); }); }); describe('isKeyOrAddressEqual', () => { - it('should return true for two matching pubkeys', () => { - expect(contract.isKeyOrAddressEqual(Z_SOME_KEY, Z_SOME_KEY)).toBe(true); - }); - - it('should return true for two matching contract addresses', () => { - expect(contract.isKeyOrAddressEqual(SOME_CONTRACT, SOME_CONTRACT)).toBe( + it('should return true for two matching pubkeys', async () => { + expect(await contract.isKeyOrAddressEqual(Z_SOME_KEY, Z_SOME_KEY)).toBe( true, ); }); - it('should return false for two different pubkeys', () => { - expect(contract.isKeyOrAddressEqual(Z_SOME_KEY, Z_OTHER_KEY)).toBe(false); + it('should return true for two matching contract addresses', async () => { + expect( + await contract.isKeyOrAddressEqual(SOME_CONTRACT, SOME_CONTRACT), + ).toBe(true); }); - it('should return false for two different contract addresses', () => { - expect(contract.isKeyOrAddressEqual(SOME_CONTRACT, OTHER_CONTRACT)).toBe( + it('should return false for two different pubkeys', async () => { + expect(await contract.isKeyOrAddressEqual(Z_SOME_KEY, Z_OTHER_KEY)).toBe( false, ); }); - it('should return false for two different address types', () => { - expect(contract.isKeyOrAddressEqual(Z_SOME_KEY, SOME_CONTRACT)).toBe( - false, - ); + it('should return false for two different contract addresses', async () => { + expect( + await contract.isKeyOrAddressEqual(SOME_CONTRACT, OTHER_CONTRACT), + ).toBe(false); }); - it('should return false for two different address types of equal value', () => { + it('should return false for two different address types', async () => { expect( - contract.isKeyOrAddressEqual( + await contract.isKeyOrAddressEqual(Z_SOME_KEY, SOME_CONTRACT), + ).toBe(false); + }); + + it('should return false for two different address types of equal value', async () => { + expect( + await contract.isKeyOrAddressEqual( contractUtils.ZERO_KEY, contractUtils.ZERO_ADDRESS, ), @@ -102,75 +110,75 @@ describe('Utils', () => { }); describe('isKeyZero', () => { - it('should return zero for the zero address', () => { - expect(contract.isKeyZero(contractUtils.ZERO_KEY.left)).toBe(true); + it('should return zero for the zero address', async () => { + expect(await contract.isKeyZero(contractUtils.ZERO_KEY.left)).toBe(true); }); - it('should not return zero for nonzero addresses', () => { - expect(contract.isKeyZero(Z_SOME_KEY.left)).toBe(false); + it('should not return zero for nonzero addresses', async () => { + expect(await contract.isKeyZero(Z_SOME_KEY.left)).toBe(false); }); }); describe('isContractAddress', () => { - it('should return true if ContractAddress', () => { - expect(contract.isContractAddress(SOME_CONTRACT)).toBe(true); + it('should return true if ContractAddress', async () => { + expect(await contract.isContractAddress(SOME_CONTRACT)).toBe(true); }); - it('should return false ZswapCoinPublicKey', () => { - expect(contract.isContractAddress(Z_SOME_KEY)).toBe(false); + it('should return false ZswapCoinPublicKey', async () => { + expect(await contract.isContractAddress(Z_SOME_KEY)).toBe(false); }); }); describe('emptyString', () => { - it('should return the empty string', () => { - expect(contract.emptyString()).toBe(EMPTY_STRING); + it('should return the empty string', async () => { + expect(await contract.emptyString()).toBe(EMPTY_STRING); }); }); describe('canonicalizeKeyOrAddress', () => { - it('should zero the right side when is_left is true', () => { + it('should zero the right side when is_left is true', async () => { const crafted = { is_left: true, left: Z_SOME_KEY.left, right: SOME_CONTRACT.right, }; - const canonical = contract.canonicalizeKeyOrAddress(crafted); + const canonical = await contract.canonicalizeKeyOrAddress(crafted); expect(canonical.is_left).toBe(true); expect(canonical.left).toEqual(Z_SOME_KEY.left); expect(canonical.right).toEqual(contractUtils.ZERO_ADDRESS.right); }); - it('should zero the left side when is_left is false', () => { + it('should zero the left side when is_left is false', async () => { const crafted = { is_left: false, left: Z_SOME_KEY.left, right: SOME_CONTRACT.right, }; - const canonical = contract.canonicalizeKeyOrAddress(crafted); + const canonical = await contract.canonicalizeKeyOrAddress(crafted); expect(canonical.is_left).toBe(false); expect(canonical.left).toEqual(contractUtils.ZERO_KEY.left); expect(canonical.right).toEqual(SOME_CONTRACT.right); }); - it('should be idempotent for canonical pubkey', () => { - const canonical = contract.canonicalizeKeyOrAddress(Z_SOME_KEY); + it('should be idempotent for canonical pubkey', async () => { + const canonical = await contract.canonicalizeKeyOrAddress(Z_SOME_KEY); expect(canonical).toEqual(Z_SOME_KEY); }); - it('should be idempotent for canonical contract address', () => { - const canonical = contract.canonicalizeKeyOrAddress(SOME_CONTRACT); + it('should be idempotent for canonical contract address', async () => { + const canonical = await contract.canonicalizeKeyOrAddress(SOME_CONTRACT); expect(canonical).toEqual(SOME_CONTRACT); }); - it('should be idempotent for already-zero pubkey', () => { - const canonical = contract.canonicalizeKeyOrAddress( + it('should be idempotent for already-zero pubkey', async () => { + const canonical = await contract.canonicalizeKeyOrAddress( contractUtils.ZERO_KEY, ); expect(canonical).toEqual(contractUtils.ZERO_KEY); }); - it('should be idempotent for already-zero contract address', () => { - const canonical = contract.canonicalizeKeyOrAddress( + it('should be idempotent for already-zero contract address', async () => { + const canonical = await contract.canonicalizeKeyOrAddress( contractUtils.ZERO_ADDRESS, ); expect(canonical).toEqual(contractUtils.ZERO_ADDRESS); @@ -178,51 +186,53 @@ describe('Utils', () => { }); describe('selfAsRecipient', () => { - it('should return the contract address as a right-variant recipient', () => { - const result = contract.selfAsRecipient(); + it('should return the contract address as a right-variant recipient', async () => { + const result = await contract.selfAsRecipient(); expect(result.is_left).toBe(false); - expect(contract.isContractAddress(result)).toBe(true); + expect(await contract.isContractAddress(result)).toBe(true); }); - it('should return a 32-byte contract address', () => { - const result = contract.selfAsRecipient(); + it('should return a 32-byte contract address', async () => { + const result = await contract.selfAsRecipient(); expect(result.right.bytes).toBeInstanceOf(Uint8Array); expect(result.right.bytes.length).toBe(32); }); - it('should return the same address on repeated calls', () => { - const first = contract.selfAsRecipient(); - const second = contract.selfAsRecipient(); + it('should return the same address on repeated calls', async () => { + const first = await contract.selfAsRecipient(); + const second = await contract.selfAsRecipient(); expect(first.right.bytes).toEqual(second.right.bytes); }); }); describe('UINT128_MAX', () => { - it('should return 2^128 - 1', () => { - expect(contract.UINT128_MAX()).toBe((1n << 128n) - 1n); + it('should return 2^128 - 1', async () => { + expect(await contract.UINT128_MAX()).toBe((1n << 128n) - 1n); }); }); describe('zeroAccount', () => { - it('should return a left variant', () => { - expect(contract.zeroAccount().is_left).toBe(true); + it('should return a left variant', async () => { + expect((await contract.zeroAccount()).is_left).toBe(true); }); - it('should have zero left and right branches', () => { - const zero = contract.zeroAccount(); + it('should have zero left and right branches', async () => { + const zero = await contract.zeroAccount(); expect(zero.left).toEqual(zeroBytes); expect(zero.right).toEqual({ bytes: zeroBytes }); }); }); describe('isTargetZero', () => { - it('should return true for the canonical zero account', () => { - expect(contract.isTargetZero(contract.zeroAccount())).toBe(true); + it('should return true for the canonical zero account', async () => { + expect(await contract.isTargetZero(await contract.zeroAccount())).toBe( + true, + ); }); - it('should return true for a zero right-variant (contract)', () => { + it('should return true for a zero right-variant (contract)', async () => { expect( - contract.isTargetZero({ + await contract.isTargetZero({ is_left: false, left: zeroBytes, right: { bytes: zeroBytes }, @@ -230,27 +240,31 @@ describe('Utils', () => { ).toBe(true); }); - it('should return false for a nonzero account (left variant)', () => { + it('should return false for a nonzero account (left variant)', async () => { const account = eitherAccount(buildAccountIdHash(createTestSK('ACCT'))); - expect(contract.isTargetZero(account)).toBe(false); + expect(await contract.isTargetZero(account)).toBe(false); }); - it('should return false for a nonzero contract (right variant)', () => { - expect(contract.isTargetZero(eitherContract('SOME_CONTRACT'))).toBe( + it('should return false for a nonzero contract (right variant)', async () => { + expect(await contract.isTargetZero(eitherContract('SOME_CONTRACT'))).toBe( false, ); }); }); describe('computeAccountId', () => { - it('should match the persistentHash derivation', () => { + it('should match the persistentHash derivation', async () => { const sk = createTestSK('SOME_SK'); - expect(contract.computeAccountId(sk)).toEqual(buildAccountIdHash(sk)); + expect(await contract.computeAccountId(sk)).toEqual( + buildAccountIdHash(sk), + ); }); - it('should produce distinct identifiers for distinct keys', () => { - const ids = ['A', 'B', 'C'].map((label) => - contract.computeAccountId(createTestSK(label)), + it('should produce distinct identifiers for distinct keys', async () => { + const ids = await Promise.all( + ['A', 'B', 'C'].map((label) => + contract.computeAccountId(createTestSK(label)), + ), ); for (let i = 0; i < ids.length; i++) { for (let j = i + 1; j < ids.length; j++) { @@ -261,8 +275,8 @@ describe('Utils', () => { }); describe('simulator wiring', () => { - it('should expose an empty public ledger via getPublicState', () => { - expect(contract.getPublicState()).toStrictEqual({}); + it('should expose an empty public ledger via getPublicState', async () => { + expect(await contract.getPublicState()).toStrictEqual({}); }); }); }); diff --git a/contracts/vitest.live.config.ts b/contracts/vitest.live.config.ts new file mode 100644 index 00000000..52377aad --- /dev/null +++ b/contracts/vitest.live.config.ts @@ -0,0 +1,24 @@ +import { configDefaults, defineConfig } from 'vitest/config'; + +// Live-backend run of the unit specs against the local stack (`make env-up`). +// Same spec files as the default dry `test`; only the backend (via +// `MIDNIGHT_BACKEND=live`) and this config differ — each `await Sim.create()` +// deploys + attaches a real contract through the registered live harness. +// +// Single fork + no parallelism: every deploy is signed by the one genesis-funded +// account, so specs must run sequentially to avoid nonce races. Generous +// timeouts: each deploy + impure call is a real proof + on-chain tx. +export default defineConfig({ + test: { + globals: true, + environment: 'node', + include: ['src/**/*.test.ts'], + exclude: [...configDefaults.exclude, 'src/archive/**'], + setupFiles: ['./test/integration/_harness/live.setup.ts'], + reporters: 'verbose', + testTimeout: 180_000, + hookTimeout: 300_000, + fileParallelism: false, + sequence: { concurrent: false }, + }, +}); diff --git a/local-env.yml b/local-env.yml new file mode 100644 index 00000000..fedb1fef --- /dev/null +++ b/local-env.yml @@ -0,0 +1,61 @@ +# WARNING: Insecure default credentials below. For local development only — do not use in production. +services: + proof-server: + image: 'midnightntwrk/proof-server:latest' + command: ['midnight-proof-server -v'] + ports: + - '6300:6300' + environment: + RUST_BACKTRACE: 'full' + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:6300/version'] + interval: 10s + timeout: 5s + retries: 20 + start_period: 10s + + indexer: + image: 'midnightntwrk/indexer-standalone:latest' + ports: + - '8088:8088' + environment: + RUST_LOG: 'indexer=debug,chain_indexer=debug,indexer_api=debug,wallet_indexer=debug,indexer_common=debug,fastrace_opentelemetry=off,info' + APP__INFRA__NODE__URL: 'ws://node:9944' + APP__APPLICATION__NETWORK_ID: 'undeployed' + APP__INFRA__STORAGE__PASSWORD: 'indexer' + APP__INFRA__PUB_SUB__PASSWORD: 'indexer' + APP__INFRA__LEDGER_STATE_STORAGE__PASSWORD: 'indexer' + APP__INFRA__SECRET: '303132333435363738393031323334353637383930313233343536373839303132' + healthcheck: + test: ['CMD-SHELL', 'cat /var/run/indexer-standalone/running'] + interval: 10s + timeout: 5s + retries: 20 + start_period: 10s + depends_on: + node: + condition: service_healthy + logging: + driver: local + options: + max-size: '10m' + max-file: '3' + + node: + image: 'midnightntwrk/midnight-node:0.22.2' + ports: + - '9944:9944' + healthcheck: + test: ['CMD', 'curl', '-f', 'http://localhost:9944/health'] + interval: 2s + timeout: 5s + retries: 20 + start_period: 5s + environment: + CFG_PRESET: 'dev' + SIDECHAIN_BLOCK_BENEFICIARY: '04bcf7ad3be7a5c790460be82a713af570f22e0f801f6659ab8e84a52be6969e' + logging: + driver: local + options: + max-size: '10m' + max-file: '3' diff --git a/yarn.lock b/yarn.lock index 873c24f2..a5435c2d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -301,7 +301,7 @@ __metadata: resolution: "@openzeppelin/compact-contracts@workspace:contracts" dependencies: "@openzeppelin/compact-cli": "npm:^0.0.2" - "@openzeppelin/compact-simulator": "npm:^0.1.0" + "@openzeppelin/compact-simulator": "npm:^0.2.0" "@tsconfig/node24": "npm:^24.0.4" "@types/node": "npm:25.9.3" "@vitest/coverage-v8": "npm:^4.1.9" @@ -312,13 +312,21 @@ __metadata: languageName: unknown linkType: soft -"@openzeppelin/compact-simulator@npm:^0.1.0": - version: 0.1.0 - resolution: "@openzeppelin/compact-simulator@npm:0.1.0" +"@openzeppelin/compact-simulator@npm:^0.2.0": + version: 0.2.0 + resolution: "@openzeppelin/compact-simulator@npm:0.2.0" dependencies: "@midnight-ntwrk/compact-runtime": "npm:0.16.0" "@midnight-ntwrk/ledger-v8": "npm:8.1.0" - checksum: 10/432bb2b4c8440e30046198471bb34cf08efec697d10a8f3cc0ce4ffe78e339eb7ea2c405e3114dbdae28c63e07936cba039105277f1ac17d521536b55aa0c7d0 + peerDependencies: + "@midnight-ntwrk/midnight-js-contracts": ^4.1.0 + "@midnight-ntwrk/midnight-js-types": ^4.1.0 + peerDependenciesMeta: + "@midnight-ntwrk/midnight-js-contracts": + optional: true + "@midnight-ntwrk/midnight-js-types": + optional: true + checksum: 10/294e53a3eaade37ae679be8aaff764239d8cb645eae0e69ceb56587b36614ed67b9a38ad641633d91a6ee65d96920475ccedec40c1f1dd734d26a86b0382ce90 languageName: node linkType: hard