Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
core_changed: ${{ steps.check-version.outputs.core_changed }}
qrcode_changed: ${{ steps.check-version.outputs.qrcode_changed }}
common_changed: ${{ steps.check-version.outputs.common_changed }}
contracts_changed: ${{ steps.check-version.outputs.contracts_changed }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Missing Trigger for Package JSON Changes

The workflow includes new logic to detect changes in contracts/package.json and a publish-contracts job. However, contracts/package.json isn't listed in the workflow's trigger paths, which prevents the workflow from automatically running to publish the contracts package when only its package.json changes.

Fix in Cursor Fix in Web

steps:
- uses: actions/checkout@v4
with:
Expand All @@ -42,6 +43,10 @@ jobs:
echo "common_changed=true" >> $GITHUB_OUTPUT
fi

if git diff HEAD^ HEAD -- contracts/package.json | grep -q '"version":' || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "contracts_changed=true" >> $GITHUB_OUTPUT
fi

publish-core:
needs: detect-changes
if: needs.detect-changes.outputs.core_changed == 'true'
Expand Down Expand Up @@ -128,3 +133,29 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
publish-contracts:
needs: detect-changes
if: needs.detect-changes.outputs.contracts_changed == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
registry-url: "https://registry.npmjs.org"
- uses: actions/checkout@v4
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Redundant Checkout Step in CI Job

The publish-contracts job contains a duplicate actions/checkout@v4 step. This second checkout is redundant and unnecessarily consumes CI resources.

Fix in Cursor Fix in Web

- name: Install Dependencies
uses: ./.github/actions/yarn-install
- name: Build package
run: |
yarn workspace @selfxyz/contracts build
- name: Publish to npm
working-directory: contracts
run: |
yarn config set npmScopes.selfxyz.npmAuthToken ${{ secrets.NPM_TOKEN }}
yarn config set npmPublishAccess public
yarn npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
20 changes: 9 additions & 11 deletions contracts/contracts/libraries/SelfUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,9 @@ library SelfUtils {
* @param forbiddenCountries Array of 3-character country codes
* @return output Array of 4 uint256 values containing packed country data
*/
function packForbiddenCountriesList(string[] memory forbiddenCountries)
internal
pure
returns (uint256[4] memory output)
{
function packForbiddenCountriesList(
string[] memory forbiddenCountries
) internal pure returns (uint256[4] memory output) {
uint256 MAX_BYTES_IN_FIELD = 31;
uint256 REQUIRED_CHUNKS = 4;

Expand Down Expand Up @@ -82,11 +80,9 @@ library SelfUtils {
* - Packed forbidden countries list for efficient circuit processing
* - Replicated OFAC settings across all verification levels
*/
function formatVerificationConfigV2(UnformattedVerificationConfigV2 memory unformattedVerificationConfigV2)
internal
pure
returns (SelfStructs.VerificationConfigV2 memory verificationConfigV2)
{
function formatVerificationConfigV2(
UnformattedVerificationConfigV2 memory unformattedVerificationConfigV2
) internal pure returns (SelfStructs.VerificationConfigV2 memory verificationConfigV2) {
bool[3] memory ofacArray;
ofacArray[0] = unformattedVerificationConfigV2.ofacEnabled;
ofacArray[1] = unformattedVerificationConfigV2.ofacEnabled;
Expand All @@ -96,7 +92,9 @@ library SelfUtils {
olderThanEnabled: unformattedVerificationConfigV2.olderThan > 0,
olderThan: unformattedVerificationConfigV2.olderThan,
forbiddenCountriesEnabled: unformattedVerificationConfigV2.forbiddenCountries.length > 0,
forbiddenCountriesListPacked: packForbiddenCountriesList(unformattedVerificationConfigV2.forbiddenCountries),
forbiddenCountriesListPacked: packForbiddenCountriesList(
unformattedVerificationConfigV2.forbiddenCountries
),
ofacEnabled: ofacArray
});
}
Expand Down
8 changes: 3 additions & 5 deletions contracts/contracts/tests/TestSelfUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import "../libraries/SelfUtils.sol";
import "../libraries/CountryCode.sol";

contract TestSelfUtils {
function testPackForbiddenCountriesList(string[] memory forbiddenCountries)
external
pure
returns (uint256[4] memory)
{
function testPackForbiddenCountriesList(
string[] memory forbiddenCountries
) external pure returns (uint256[4] memory) {
return SelfUtils.packForbiddenCountriesList(forbiddenCountries);
}

Expand Down
49 changes: 29 additions & 20 deletions contracts/test/unit/SelfUtils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ describe("SelfUtils", function () {
const tsResult = packForbiddenCountriesList(input);

// Convert TypeScript result to the same format as contract result
const expectedResults = tsResult.map(hex => BigInt(hex));
const expectedResults = tsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(contractResult[i]).to.equal(expectedResults[i]);
Expand All @@ -31,7 +31,7 @@ describe("SelfUtils", function () {
const contractResult = await testSelfUtils.testPackForbiddenCountriesList(input);
const tsResult = packForbiddenCountriesList(input);

const expectedResults = tsResult.map(hex => BigInt(hex));
const expectedResults = tsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(contractResult[i]).to.equal(expectedResults[i]);
Expand All @@ -43,7 +43,7 @@ describe("SelfUtils", function () {
const contractResult = await testSelfUtils.testPackForbiddenCountriesList(input);
const tsResult = packForbiddenCountriesList(input);

const expectedResults = tsResult.map(hex => BigInt(hex));
const expectedResults = tsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(contractResult[i]).to.equal(expectedResults[i]);
Expand All @@ -53,14 +53,19 @@ describe("SelfUtils", function () {
it("should match contract and TypeScript implementation for maximum capacity", async function () {
// Create array that fills multiple chunks
const countries = [];
for (let i = 0; i < 41; i++) { // 41 * 3 = 123 bytes, needs 4 chunks (31 bytes each)
countries.push(String.fromCharCode(65 + (i % 26)) + String.fromCharCode(65 + ((i + 1) % 26)) + String.fromCharCode(65 + ((i + 2) % 26)));
for (let i = 0; i < 41; i++) {
// 41 * 3 = 123 bytes, needs 4 chunks (31 bytes each)
countries.push(
String.fromCharCode(65 + (i % 26)) +
String.fromCharCode(65 + ((i + 1) % 26)) +
String.fromCharCode(65 + ((i + 2) % 26)),
);
}

const contractResult = await testSelfUtils.testPackForbiddenCountriesList(countries);
const tsResult = packForbiddenCountriesList(countries);

const expectedResults = tsResult.map(hex => BigInt(hex));
const expectedResults = tsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(contractResult[i]).to.equal(expectedResults[i]);
Expand All @@ -72,7 +77,7 @@ describe("SelfUtils", function () {
const contractResult = await testSelfUtils.testPackForbiddenCountriesList(input);
const tsResult = packForbiddenCountriesList(input);

const expectedResults = tsResult.map(hex => BigInt(hex));
const expectedResults = tsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(contractResult[i]).to.equal(expectedResults[i]);
Expand All @@ -84,43 +89,47 @@ describe("SelfUtils", function () {

// Both implementations should reject this
expect(() => packForbiddenCountriesList(input)).to.throw("Invalid country code");
await expect(testSelfUtils.testPackForbiddenCountriesList(input))
.to.be.revertedWith("Invalid country code: must be exactly 3 characters long");
await expect(testSelfUtils.testPackForbiddenCountriesList(input)).to.be.revertedWith(
"Invalid country code: must be exactly 3 characters long",
);
});

it("should reject country codes that are too long", async function () {
const input = ["USAA"]; // Too long

// Both implementations should reject this
expect(() => packForbiddenCountriesList(input)).to.throw("Invalid country code");
await expect(testSelfUtils.testPackForbiddenCountriesList(input))
.to.be.revertedWith("Invalid country code: must be exactly 3 characters long");
await expect(testSelfUtils.testPackForbiddenCountriesList(input)).to.be.revertedWith(
"Invalid country code: must be exactly 3 characters long",
);
});

it("should reject empty country codes", async function () {
const input = [""]; // Empty

// Both implementations should reject this
expect(() => packForbiddenCountriesList(input)).to.throw("Invalid country code");
await expect(testSelfUtils.testPackForbiddenCountriesList(input))
.to.be.revertedWith("Invalid country code: must be exactly 3 characters long");
await expect(testSelfUtils.testPackForbiddenCountriesList(input)).to.be.revertedWith(
"Invalid country code: must be exactly 3 characters long",
);
});

it("should handle mixed valid and invalid codes consistently", async function () {
const input = ["USA", "GB"]; // One valid, one invalid

// Both implementations should reject this
expect(() => packForbiddenCountriesList(input)).to.throw("Invalid country code");
await expect(testSelfUtils.testPackForbiddenCountriesList(input))
.to.be.revertedWith("Invalid country code: must be exactly 3 characters long");
await expect(testSelfUtils.testPackForbiddenCountriesList(input)).to.be.revertedWith(
"Invalid country code: must be exactly 3 characters long",
);
});

it("should handle special characters in country codes", async function () {
const input = ["U-A", "GB1", "FR@"];
const contractResult = await testSelfUtils.testPackForbiddenCountriesList(input);
const tsResult = packForbiddenCountriesList(input);

const expectedResults = tsResult.map(hex => BigInt(hex));
const expectedResults = tsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(contractResult[i]).to.equal(expectedResults[i]);
Expand Down Expand Up @@ -159,7 +168,7 @@ describe("SelfUtils", function () {
const contractResult = await testSelfUtils.testPackForbiddenCountriesList(forbiddenCountries);
const tsResult = packForbiddenCountriesList(forbiddenCountries);

const expectedResults = tsResult.map(hex => BigInt(hex));
const expectedResults = tsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(contractResult[i]).to.equal(expectedResults[i]);
Expand All @@ -176,7 +185,7 @@ describe("SelfUtils", function () {
// Create equivalent array in TypeScript for comparison
const forbiddenCountries = ["CHN", "RUS", "IRN", "PRK", "CUB", "SYR", "AFG", "SOM"];
const tsResult = packForbiddenCountriesList(forbiddenCountries);
const expectedResults = tsResult.map(hex => BigInt(hex));
const expectedResults = tsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(contractResult[i]).to.equal(expectedResults[i]);
Expand All @@ -188,7 +197,7 @@ describe("SelfUtils", function () {
const highRiskResult = await testSelfUtils.testPackHighRiskCountries();
const highRiskCountries = ["AFG", "SOM", "SDN", "YEM"];
const highRiskTsResult = packForbiddenCountriesList(highRiskCountries);
const highRiskExpected = highRiskTsResult.map(hex => BigInt(hex));
const highRiskExpected = highRiskTsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(highRiskResult[i]).to.equal(highRiskExpected[i]);
Expand All @@ -198,7 +207,7 @@ describe("SelfUtils", function () {
const euResult = await testSelfUtils.testPackEUCountries();
const euCountries = ["DEU", "FRA", "ITA", "ESP", "NLD"];
const euTsResult = packForbiddenCountriesList(euCountries);
const euExpected = euTsResult.map(hex => BigInt(hex));
const euExpected = euTsResult.map((hex) => BigInt(hex));

for (let i = 0; i < 4; i++) {
expect(euResult[i]).to.equal(euExpected[i]);
Expand Down
Loading