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

it compiles #6

Merged
merged 20 commits into from
Jun 11, 2024
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
2 changes: 1 addition & 1 deletion .github/workflows/gas-snapshot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
run: |
set -euo pipefail
# grep -E '^test' -- skip over test results, just get diffs
forge snapshot --via-ir --diff \
FOUNDRY_PROFILE=ir forge snapshot --diff \
| grep -E '^test' \
| tee .gas-snapshot.new
env:
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ jobs:
- name: Run Forge build
run: |
forge --version
forge build --via-ir --sizes
FOUNDRY_PROFILE=ir forge build --sizes
id: build

- name: Run Forge tests
run: |
forge test --via-ir
FOUNDRY_PROFILE=ir forge test
id: test
env:
NODE_PROVIDER_BYPASS_KEY: ${{ secrets.NODE_PROVIDER_BYPASS_KEY }}
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ evm_version = "paris"

libs = [ "./lib" ]

[profile.ir]
via_ir = true
optimizer = true
optimizer_runs = 100000000

bytecode_hash = "none"
cbor_metadata = false
cbor_metadata = false
2 changes: 1 addition & 1 deletion script/deploy-quark-scripts.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ else
etherscan_args=""
fi

forge script --via-ir \
FOUNDRY_PROFILE=ir forge script \
$rpc_args \
$wallet_args \
$etherscan_args \
Expand Down
2 changes: 1 addition & 1 deletion script/prepare-release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ artifact_name="quark-scripts-out.${release_name}.zip"
artifact_note="Compiled ABI"

printf 'building full project...\n'
forge build --via-ir
FOUNDRY_PROFILE=ir forge build

printf 'preparing release archive "%s"...\n' ${release_name}
zip --recurse-paths "${artifact_name}" out/*
Expand Down
2 changes: 1 addition & 1 deletion script/update-snapshot.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/bin/bash

forge snapshot --via-ir
FOUNDRY_PROFILE=ir forge snapshot
91 changes: 91 additions & 0 deletions src/builder/Accounts.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.23;

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

library Accounts {
struct ChainAccounts {
uint256 chainId;
QuarkState[] quarkStates;
AssetPositions[] assetPositionsList;
}

// We map this to the Portfolio data structure that the client will already have.
// This includes fields that builder may not necessarily need, however it makes
// the client encoding that much simpler.
struct QuarkState {
address account;
bool hasCode;
bool isQuark;
string quarkVersion;
uint96 quarkNextNonce;
}

// Similarly, this is designed to intentionally reduce the encoding burden for the client
// by making it equivalent in structure to data already in portfolios.
struct AssetPositions {
address asset;
string symbol;
uint256 decimals;
uint256 usdPrice;
AccountBalance[] accountBalances;
}

struct AccountBalance {
address account;
uint256 balance;
}

function findChainAccounts(uint256 chainId, ChainAccounts[] memory chainAccountsList)
internal
pure
returns (ChainAccounts memory found)
{
for (uint256 i = 0; i < chainAccountsList.length; ++i) {
if (chainAccountsList[i].chainId == chainId) {
return found = chainAccountsList[i];
}
}
}

function findAssetPositions(string memory assetSymbol, AssetPositions[] memory assetPositionsList)
internal
pure
returns (AssetPositions memory found)
{
for (uint256 i = 0; i < assetPositionsList.length; ++i) {
if (Strings.stringEqIgnoreCase(assetSymbol, assetPositionsList[i].symbol)) {
return found = assetPositionsList[i];
}
}
}

function findAssetPositions(string memory assetSymbol, uint256 chainId, ChainAccounts[] memory chainAccountsList)
internal
pure
returns (AssetPositions memory found)
{
ChainAccounts memory chainAccounts = findChainAccounts(chainId, chainAccountsList);
return findAssetPositions(assetSymbol, chainAccounts.assetPositionsList);
}

function findQuarkState(address account, Accounts.QuarkState[] memory quarkStates)
internal
pure
returns (Accounts.QuarkState memory state)
{
for (uint256 i = 0; i < quarkStates.length; ++i) {
if (quarkStates[i].account == account) {
return state = quarkStates[i];
}
}
}

function sumBalances(AssetPositions memory assetPositions) internal pure returns (uint256) {
uint256 totalBalance = 0;
for (uint256 j = 0; j < assetPositions.accountBalances.length; ++j) {
kevincheng96 marked this conversation as resolved.
Show resolved Hide resolved
totalBalance += assetPositions.accountBalances[j].balance;
}
return totalBalance;
}
}
173 changes: 173 additions & 0 deletions src/builder/Actions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// SPDX-License-Identifier: BSD-3-Clause
pragma solidity ^0.8.23;

import {CCTP} from "./BridgeRoutes.sol";
import {Strings} from "./Strings.sol";
import {Accounts} from "./Accounts.sol";
import {CodeJarHelper} from "./CodeJarHelper.sol";

import {TransferActions} from "../DeFiScripts.sol";

import {IQuarkWallet} from "quark-core/src/interfaces/IQuarkWallet.sol";

library Actions {
/* ===== Constants ===== */
string constant PAYMENT_METHOD_OFFCHAIN = "OFFCHAIN";
string constant PAYMENT_METHOD_PAYCALL = "PAY_CALL";
string constant PAYMENT_METHOD_QUOTECALL = "QUOTE_CALL";

string constant ACTION_TYPE_BRIDGE = "BRIDGE";
string constant ACTION_TYPE_TRANSFER = "TRANSFER";

/* ===== Custom Errors ===== */

error InvalidAssetForBridge();

/* ===== Input Types ===== */

struct TransferAsset {
Accounts.ChainAccounts[] chainAccountsList;
string assetSymbol;
uint256 amount;
uint256 chainId;
address sender;
address recipient;
}

struct BridgeUSDC {
Accounts.ChainAccounts[] chainAccountsList;
string assetSymbol;
uint256 amount;
uint256 originChainId;
address sender;
uint256 destinationChainId;
address recipient;
}

/* ===== Output Types ===== */

// With Action, we try to define fields that are as 1:1 as possible with
// the simulate endpoint request schema.
struct Action {
uint256 chainId;
address quarkAccount;
string actionType;
bytes actionContext;
// One of the PAYMENT_METHOD_* constants.
string paymentMethod;
Copy link
Contributor

Choose a reason for hiding this comment

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

I guess we should put a TODO: for paymentMethod?
It seems I can't find where it gets initialized or specified anywhere 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

This is set in #8

// Address of payment token on chainId.
// Null address if the payment method was OFFCHAIN.
address paymentToken;
uint256 paymentMaxCost;
}

struct TransferActionContext {
uint256 amount;
uint256 price;
address token;
uint256 chainId;
address recipient;
}

struct BridgeActionContext {
uint256 amount;
uint256 price;
address token;
uint256 chainId;
address recipient;
uint256 destinationChainId;
}

function bridgeUSDC(BridgeUSDC memory bridge)
internal
pure
returns (IQuarkWallet.QuarkOperation memory /*, QuarkAction memory*/ )
{
if (!Strings.stringEqIgnoreCase(bridge.assetSymbol, "USDC")) {
revert InvalidAssetForBridge();
}

Accounts.ChainAccounts memory originChainAccounts =
Accounts.findChainAccounts(bridge.originChainId, bridge.chainAccountsList);

Accounts.AssetPositions memory originUSDCPositions =
Accounts.findAssetPositions("USDC", originChainAccounts.assetPositionsList);

Accounts.QuarkState memory accountState =
Accounts.findQuarkState(bridge.sender, originChainAccounts.quarkStates);

bytes[] memory scriptSources = new bytes[](1);
scriptSources[0] = CCTP.bridgeScriptSource();

// CCTP bridge
return IQuarkWallet.QuarkOperation({
nonce: accountState.quarkNextNonce,
scriptAddress: CodeJarHelper.getCodeAddress(bridge.originChainId, scriptSources[0]),
scriptCalldata: CCTP.encodeBridgeUSDC(
bridge.originChainId, bridge.destinationChainId, bridge.amount, bridge.recipient, originUSDCPositions.asset
),
scriptSources: scriptSources,
expiry: 99999999999 // TODO: handle expiry
});
}

// TODO: Handle paycall
function transferAsset(TransferAsset memory transfer)
internal
pure
returns (IQuarkWallet.QuarkOperation memory, Action memory)
{
bytes[] memory scriptSources = new bytes[](1);
scriptSources[0] = type(TransferActions).creationCode;

Accounts.ChainAccounts memory accounts =
Accounts.findChainAccounts(transfer.chainId, transfer.chainAccountsList);

Accounts.AssetPositions memory assetPositions =
Accounts.findAssetPositions(transfer.assetSymbol, accounts.assetPositionsList);

Accounts.QuarkState memory accountState = Accounts.findQuarkState(transfer.sender, accounts.quarkStates);

bytes memory scriptCalldata;
if (Strings.stringEqIgnoreCase(transfer.assetSymbol, "ETH")) {
// Native token transfer
scriptCalldata = abi.encodeWithSelector(
TransferActions.transferNativeToken.selector, transfer.recipient, transfer.amount
);
} else {
// ERC20 transfer
scriptCalldata = abi.encodeWithSelector(
TransferActions.transferERC20Token.selector, assetPositions.asset, transfer.recipient, transfer.amount
);
}
// Construct QuarkOperation
IQuarkWallet.QuarkOperation memory quarkOperation = IQuarkWallet.QuarkOperation({
nonce: accountState.quarkNextNonce,
scriptAddress: CodeJarHelper.getCodeAddress(transfer.chainId, type(TransferActions).creationCode),
scriptCalldata: scriptCalldata,
scriptSources: scriptSources,
expiry: 99999999999 // TODO: handle expiry
});

// Construct Action
TransferActionContext memory transferActionContext = TransferActionContext({
amount: transfer.amount,
price: assetPositions.usdPrice,
token: assetPositions.asset,
chainId: transfer.chainId,
recipient: transfer.recipient
});
Action memory action = Actions.Action({
chainId: transfer.chainId,
quarkAccount: transfer.sender,
actionType: ACTION_TYPE_TRANSFER,
actionContext: abi.encode(transferActionContext),
paymentMethod: PAYMENT_METHOD_OFFCHAIN,
// Null address for OFFCHAIN payment.
paymentToken: address(0),
paymentMaxCost: 0
});

return (quarkOperation, action);
}
}
Loading
Loading