Skip to content

Commit

Permalink
Add contracts
Browse files Browse the repository at this point in the history
  • Loading branch information
wshino committed Oct 3, 2024
1 parent dea6178 commit 27600c2
Show file tree
Hide file tree
Showing 15 changed files with 1,403 additions and 6 deletions.
14 changes: 14 additions & 0 deletions packages/contracts/foundry.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[profile.default]
src = "src"
out = "artifacts"
libs = ["../../node_modules", "lib"]
optimizer = true
optimizer-runs = 20_000

solc = "0.8.26"

# See more config options https://github.com/foundry-rs/foundry/tree/master/config

# OpenZeppelin
build_info = true
extra_output = ["storageLayout"]
29 changes: 29 additions & 0 deletions packages/contracts/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "@zk-jwt/zk-jwt-contracts",
"version": "0.0.1",
"license": "MIT",
"scripts": {
"build": "forge build --skip '*ZKSync*'",
"test": "forge test",
"lint": "solhint 'src/**/*.sol'"
},
"dependencies": {
"@openzeppelin/contracts": "^5.0.0",
"@openzeppelin/contracts-upgradeable": "^5.0.0",
"@zk-email/contracts": "^6.1.5",
"solady": "^0.0.123",
"solidity-stringutils": "github:LayerZero-Labs/solidity-stringutils"
},
"devDependencies": {
"ds-test": "https://github.com/dapphub/ds-test",
"forge-std": "https://github.com/foundry-rs/forge-std",
"solhint": "^3.6.1"
},
"files": [
"/src",
"foundry.toml",
"package.json",
"README.md",
"remappings.txt"
]
}
6 changes: 6 additions & 0 deletions packages/contracts/remappings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@openzeppelin/=../../node_modules/@openzeppelin
@openzeppelin/contracts-upgradeable/=../../node_modules/@openzeppelin/contracts-upgradeable
@zk-email/=../../node_modules/@zk-email
forge-std/=../../node_modules/forge-std/src
ds-test/=../../node_modules/ds-test/src
solidity-stringutils/=../../node_modules/solidity-stringutils/
11 changes: 11 additions & 0 deletions packages/contracts/src/interfaces/IJwtGroth16Verifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

interface IJwtGroth16Verifier {
function verifyProof(
uint[2] calldata _pA,
uint[2][2] calldata _pB,
uint[2] calldata _pC,
uint[29] calldata _pubSignals
) external view returns (bool);
}
169 changes: 169 additions & 0 deletions packages/contracts/src/libraries/CommandUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/utils/Strings.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import "./DecimalUtils.sol";

library CommandUtils {
bytes16 private constant LOWER_HEX_DIGITS = "0123456789abcdef";
bytes16 private constant UPPER_HEX_DIGITS = "0123456789ABCDEF";
string public constant STRING_MATCHER = "{string}";
string public constant UINT_MATCHER = "{uint}";
string public constant INT_MATCHER = "{int}";
string public constant DECIMALS_MATCHER = "{decimals}";
string public constant ETH_ADDR_MATCHER = "{ethAddr}";

function addressToHexString(
address addr,
uint stringCase
) internal pure returns (string memory) {
if (stringCase == 0) {
return addressToChecksumHexString(addr);
} else if (stringCase == 1) {
return Strings.toHexString(addr);
} else if (stringCase == 2) {
return lowerToUpperCase(Strings.toHexString(addr));
} else {
revert("invalid stringCase");
}
}

function addressToChecksumHexString(
address addr
) internal pure returns (string memory) {
string memory lowerCaseAddrWithOx = Strings.toHexString(addr);

bytes memory lowerCaseAddr = new bytes(40); // Remove 0x added by the OZ lib
for (uint8 i = 2; i < 42; i++) {
lowerCaseAddr[i - 2] = bytes(lowerCaseAddrWithOx)[i];
}

// Hash of lowercase addr
uint256 lowerCaseHash = uint256(
keccak256(abi.encodePacked(lowerCaseAddr))
);

// Result hex = 42 chars with 0x prefix
bytes memory result = new bytes(42);
result[0] = "0";
result[1] = "x";

// Shift 24 bytes (96 bits) to the right; as we only need first 20 bytes of the hash to compare
lowerCaseHash >>= 24 * 4;

uint256 intAddr = uint256(uint160(addr));

for (uint8 i = 41; i > 1; --i) {
uint8 hashChar = uint8(lowerCaseHash & 0xf); // Get last char of the hex
uint8 addrChar = uint8(intAddr & 0xf); // Get last char of the address

if (hashChar >= 8) {
result[i] = UPPER_HEX_DIGITS[addrChar];
} else {
result[i] = LOWER_HEX_DIGITS[addrChar];
}

// Remove last char from both hash and addr
intAddr >>= 4;
lowerCaseHash >>= 4;
}

return string(result);
}

function lowerToUpperCase(
string memory hexStr
) internal pure returns (string memory) {
bytes memory bytesStr = bytes(hexStr);
for (uint i = 0; i < bytesStr.length; i++) {
if (bytesStr[i] >= 0x61 && bytesStr[i] <= 0x66) {
bytesStr[i] = bytes1(uint8(bytesStr[i]) - 32);
}
}
return string(bytesStr);
}

/// @notice Convert bytes to hex string without 0x prefix
/// @param data bytes to convert
function bytesToHexString(
bytes memory data
) public pure returns (string memory) {
bytes memory hexChars = "0123456789abcdef";
bytes memory hexString = new bytes(2 * data.length);

for (uint256 i = 0; i < data.length; i++) {
uint256 value = uint256(uint8(data[i]));
hexString[2 * i] = hexChars[value >> 4];
hexString[2 * i + 1] = hexChars[value & 0xf];
}

return string(hexString);
}

/// @notice Calculate the expected command.
/// @param commandParams Params to be used in the command
/// @param template Template to be used for the command
/// @param stringCase Case of the ethereum address string to be used for the command - 0: checksum, 1: lowercase, 2: uppercase
function computeExpectedCommand(
bytes[] memory commandParams,
string[] memory template,
uint stringCase
) public pure returns (string memory expectedCommand) {
// Construct an expectedCommand from template and the values of commandParams.
uint8 nextParamIndex = 0;
string memory stringParam;
bool isParamExist;
for (uint8 i = 0; i < template.length; i++) {
isParamExist = true;
if (Strings.equal(template[i], STRING_MATCHER)) {
string memory param = abi.decode(
commandParams[nextParamIndex],
(string)
);
stringParam = param;
} else if (Strings.equal(template[i], UINT_MATCHER)) {
uint256 param = abi.decode(
commandParams[nextParamIndex],
(uint256)
);
stringParam = Strings.toString(param);
} else if (Strings.equal(template[i], INT_MATCHER)) {
int256 param = abi.decode(
commandParams[nextParamIndex],
(int256)
);
stringParam = Strings.toStringSigned(param);
} else if (Strings.equal(template[i], DECIMALS_MATCHER)) {
uint256 param = abi.decode(
commandParams[nextParamIndex],
(uint256)
);
stringParam = DecimalUtils.uintToDecimalString(param);
} else if (Strings.equal(template[i], ETH_ADDR_MATCHER)) {
address param = abi.decode(
commandParams[nextParamIndex],
(address)
);
stringParam = addressToHexString(param, stringCase);
} else {
isParamExist = false;
stringParam = template[i];
}

if (i > 0) {
expectedCommand = string(
abi.encodePacked(expectedCommand, " ")
);
}
expectedCommand = string(
abi.encodePacked(expectedCommand, stringParam)
);
if (isParamExist) {
nextParamIndex++;
}
}
return expectedCommand;
}
}
84 changes: 84 additions & 0 deletions packages/contracts/src/libraries/DecimalUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "@openzeppelin/contracts/utils/Strings.sol";

/// @title DecimalUtils
/// @notice DecimalUtils library for converting uint256 to string with decimal places
library DecimalUtils {
/// @notice Convert uint256 to human readable string with decimal places
/// @param value uint256 value to convert
/// @return string representation of value with decimal places
function uintToDecimalString(uint256 value) public pure returns (string memory) {
return uintToDecimalString(value, 18);
}

/// @notice Convert uint256 to human readable string with decimal places
/// @param value uint256 value to convert
/// @param decimal number of decimal places
/// @return string representation of value with decimal places
function uintToDecimalString(uint256 value, uint decimal) public pure returns (string memory) {
// Convert value to string in wei format (no decimals)
bytes memory valueBytes = bytes(Strings.toString(value));
uint8 valueLength = uint8(valueBytes.length);

// Create result array with max length
// If less than 18 decimals, then 2 extra for "0.", otherwise one extra for "."
bytes memory result = new bytes(valueLength > decimal ? valueLength + 1 : decimal + 2);
uint8 resultLength = uint8(result.length);

// We will be populating result array by copying from value array from last to first index
// Difference between result and value array index when copying
// If more than 18, then 1 index diff for ".", otherwise actual diff in length
uint delta = valueLength > decimal ? 1 : resultLength - valueLength;

// Boolean to indicate if we found a non-zero digit when scanning from last to first index
bool foundNonZeroDecimal;

uint8 actualResultLen = 0;

// In each iteration we fill one index of result array (starting from end)
for (uint8 i = resultLength - 1; i >= 0; i--) {
// Check if we have reached the index where we need to add decimal point
if (i == resultLength - decimal - 1) {
// No need to add "." if there was no value in decimal places
if (foundNonZeroDecimal) {
result[i] = ".";
actualResultLen++;
}
// Set delta to 0, as we have already added decimal point (only for valueLength > 18)
delta = 0;
}
// If valueLength < 18 and we have copied everything, fill zeros
else if (valueLength <= decimal && i < resultLength - valueLength) {
result[i] = "0";
actualResultLen++;
}
// If non-zero decimal is found, or decimal point inserted (delta == 0), copy from value array
else if (foundNonZeroDecimal || delta == 0) {
result[i] = valueBytes[i - delta];
actualResultLen++;
}
// If we find non-zero decumal for the first time (trailing zeros are skipped)
else if (valueBytes[i - delta] != "0") {
result[i] = valueBytes[i - delta];
actualResultLen++;
foundNonZeroDecimal = true;
}

// To prevent the last i-- underflow
if (i == 0) {
break;
}
}

// Create final result array with correct length
bytes memory compactResult = new bytes(actualResultLen);
for (uint8 i = 0; i < actualResultLen; i++) {
compactResult[i] = result[i];
}

return string(compactResult);
}
}

Loading

0 comments on commit 27600c2

Please sign in to comment.