Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Venmo V3 contracts #413

Closed
wants to merge 1 commit into from
Closed
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
61 changes: 61 additions & 0 deletions contracts-ramp/contracts/external/StringArrayUtils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
Copyright 2020 Set Labs Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

SPDX-License-Identifier: Apache-2.0
*/

pragma solidity ^0.8.18;

/**
* @title StringArrayUtils
* @author Set Protocol
*
* Utility functions to handle String Arrays
*/
library StringArrayUtils {

/**
* Finds the index of the first occurrence of the given element.
* @param A The input string to search
* @param a The value to find
* @return Returns (index and isIn) for the first occurrence starting from index 0
*/
function indexOf(string[] memory A, string memory a) internal pure returns (uint256, bool) {
uint256 length = A.length;
for (uint256 i = 0; i < length; i++) {
if (keccak256(bytes(A[i])) == keccak256(bytes(a))) {
return (i, true);
}
}
return (type(uint256).max, false);
}

/**
* @param A The input array to search
* @param a The string to remove
*/
function removeStorage(string[] storage A, string memory a)
internal
{
(uint256 index, bool isIn) = indexOf(A, a);
if (!isIn) {
revert("String not in array.");
} else {
uint256 lastIndex = A.length - 1; // If the array would be empty, the previous line would throw, so no underflow here
if (index != lastIndex) { A[index] = A[lastIndex]; }
A.pop();
}
}
}
191 changes: 191 additions & 0 deletions contracts-ramp/contracts/lib/ClaimVerifier.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
//SPDX-License-Identifier: MIT

import { Claims } from "@reclaimprotocol/verifier-solidity-sdk/contracts/Claims.sol";

pragma solidity ^0.8.18;

library ClaimVerifier {

/**
* Find the end index of target string in the data string. Returns the end index + 1 if
* the target string in the data string if found. Returns type(uint256).max if:
* - Target is longer than data
* - Target is not found
* Parts of the code are adapted from: https://basescan.org/address/0x7281630e4346dd4c0b7ae3b4689c1d0102741410#code
*/
function findSubstringEndIndex(
string memory data,
string memory target
) public pure returns (uint256) {
bytes memory dataBytes = bytes(data);
bytes memory targetBytes = bytes(target);

if (dataBytes.length < targetBytes.length) {
return type(uint256).max;
}

// Find start of target
for (uint i = 0; i <= dataBytes.length - targetBytes.length; i++) {
bool isMatch = true;

for (uint j = 0; j < targetBytes.length && isMatch; j++) {
if (dataBytes[i + j] != targetBytes[j]) {
isMatch = false;
break;
}
}

if (isMatch) {
return i + targetBytes.length; // Return end index + 1
}
}

return type(uint256).max;
}

/**
* Extracts given target field value from context in claims. Extracts only ONE value.
* Pass prefix formatted with quotes, for example '"providerHash\":\"'
* Parts of the code are adapted from: https://basescan.org/address/0x7281630e4346dd4c0b7ae3b4689c1d0102741410#code
*
* @param data Context string from which target value needs to be extracted
* @param prefix Prefix of the target value that needs to be extracted
*/
function extractFieldFromContext(
string memory data,
string memory prefix
) public pure returns (string memory) {
// Find end index of prefix; which is the start index of the value
uint256 start = findSubstringEndIndex(data, prefix);

bytes memory dataBytes = bytes(data);
if (start == dataBytes.length) {
return ""; // Prefix not found. Malformed or missing message
}

// Find the end of the VALUE, assuming it ends with a quote not preceded by a backslash
uint256 end = start;
while (
end < dataBytes.length &&
!(dataBytes[end] == '"' && dataBytes[end - 1] != "\\")
) {
end++;
}
if (end <= start) {
return ""; // Malformed or missing message
}
bytes memory contextMessage = new bytes(end - start);
for (uint i = start; i < end; i++) {
contextMessage[i - start] = dataBytes[i];
}
return string(contextMessage);
}


/**
* Extracts ALL values from context in a single pass. Context is stored as serialized JSON string with
* two keys: extractedParameters and providerHash. ExtractedParameters itself is a JSON string with
* key-value pairs. This function returns extracted individual values from extractedParameters along
* with providerHash (if extractProviderHash is true). Use maxValues to limit the number of expected values
* to be extracted from extractedParameters. In most cases, one would need to extract all values from
* extractedParameters and providerHash, hence use this function over calling extractFieldFromContext
* multiple times.
*
* @param data Context string from which target value needs to be extracted
* @param maxValues Maximum number of values to be extracted from extractedParameters
* @param extractProviderHash Extracts and returns providerHash if true
*/
function extractAllFromContext(
string memory data,
uint8 maxValues,
bool extractProviderHash
) public pure returns (string[] memory) {

require(maxValues > 0, "Max values must be greater than 0");

bytes memory dataBytes = bytes(data);
uint index = 0;

bytes memory extractedParametersBytes = bytes('{\"extractedParameters\":{\"');
for (uint i = 0; i < extractedParametersBytes.length; i++) {
require(dataBytes[index + i] == extractedParametersBytes[i], "Extraction failed. Malformed extractedParameters");
}
index += extractedParametersBytes.length;

bool isValue = false; // starts with a key right after '{\"extractedParameters\":{\"'
uint valuesFound = 0;

uint[] memory valueIndices = new uint[](extractProviderHash ? 2 * (maxValues + 1): 2 * maxValues);

while (
index < dataBytes.length
) {
// Keep incrementing until '"', escaped quotes are not considered
if (!(dataBytes[index] == '"' && dataBytes[index - 1] != "\\")) {
index++;
continue;
}

if (!isValue) {
// \":\" (3 chars)
require(dataBytes[index + 1] == ":" && dataBytes[index + 2] == '\"', "Extraction failed. Malformed data 1");
index += 3; // move it after \"
isValue = true;
valueIndices[2 * valuesFound] = index; // start index
} else {
// \",\" (3 chars) or \"}, (3 chars)
// \"}} is not supported, there should always be a providerHash
require(
dataBytes[index + 1] == "," && dataBytes[index + 2] == '\"' ||
dataBytes[index + 1] == '}' && dataBytes[index + 2] == ",",
"Extraction failed. Malformed data 2"
);
valueIndices[2 * valuesFound + 1] = index; // end index
valuesFound++;

if (dataBytes[index + 1] == ",") {
// Revert if valuesFound == maxValues and next char is a comma as there will be more values
require(valuesFound != maxValues, "Extraction failed. Exceeded max values");
index += 3;
isValue = false;
} else { // index + 1 = "}"
index += 3;
break; // end of extractedParameters
}
}
}

if (extractProviderHash) {
bytes memory providerHashParamBytes = bytes("\"providerHash\":\"");
for (uint i = 0; i < providerHashParamBytes.length; i++) {
require(dataBytes[index + i] == providerHashParamBytes[i], "Extraction failed. Malformed providerHash");
}
index += providerHashParamBytes.length;

// final indices tuple in valueIndices will be for star and end indices of provider hash
valueIndices[2 * valuesFound] = index;
// Keep incrementing until '"'
while (
index < dataBytes.length && dataBytes[index] != '"'
) {
index++;
}
valueIndices[2 * valuesFound + 1] = index;
valuesFound++;
}

string[] memory values = new string[](valuesFound);

for (uint i = 0; i < valuesFound; i++) {
uint startIndex = valueIndices[2 * i];
uint endIndex = valueIndices[2 * i + 1];
bytes memory contextValue = new bytes(endIndex - startIndex);
for (uint j = startIndex; j < endIndex; j++) {
contextValue[j - startIndex] = dataBytes[j];
}
values[i] = string(contextValue);
}

return values;
}
}
47 changes: 47 additions & 0 deletions contracts-ramp/contracts/lib/DateParsing.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//SPDX-License-Identifier: MIT

import { DateTime } from "../external/DateTime.sol";

import { StringConversionUtils } from "./StringConversionUtils.sol";

pragma solidity ^0.8.18;

library DateParsing {

using StringConversionUtils for string;

/**
* @notice Iterates through every character in the date string and splits the string at each dash, "T", or colon. Function will revert
* if there are not 6 substrings formed from the split. The substrings are then converted to uints and passed to the DateTime lib
* to get the unix timestamp. This function is SPECIFIC TO THE DATE FORMAT YYYY-MM-DDTHH:MM:SS, not suitable for use with other date
* formats. It returns UTC timestamps.
*
* @param _dateString Date string to be converted to a UTC timestamp
*/
function _dateStringToTimestamp(string memory _dateString) internal pure returns (uint256 utcTimestamp) {
string[6] memory extractedStrings;
uint256 breakCounter;
uint256 lastBreak;
for (uint256 i = 0; i < bytes(_dateString).length; i++) {
if (bytes(_dateString)[i] == 0x2d || bytes(_dateString)[i] == 0x3a || bytes(_dateString)[i] == 0x54) {
extractedStrings[breakCounter] = _dateString.substring(lastBreak, i);
lastBreak = i + 1;
breakCounter++;
}
}
// Add last substring to array
extractedStrings[breakCounter] = _dateString.substring(lastBreak, bytes(_dateString).length);

// Check that exactly 6 substrings were found (string is split at 5 different places)
require(breakCounter == 5, "Invalid date string");

utcTimestamp = DateTime.timestampFromDateTime(
extractedStrings[0].stringToUint(0), // year
extractedStrings[1].stringToUint(0), // month
extractedStrings[2].stringToUint(0), // day
extractedStrings[3].stringToUint(0), // hour
extractedStrings[4].stringToUint(0), // minute
extractedStrings[5].stringToUint(0) // second
);
}
}
Loading
Loading