Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ecba4d3
feat: AAVE external lib and Approve internal lib (SC-1182) (#162)
deluca-mike Nov 10, 2025
58a0735
feat: Add ERC4626 lib (SC-1233) (#188)
deluca-mike Nov 10, 2025
2aed93a
fix: Use relative paths (#191)
supercontracts Nov 13, 2025
651885e
feat: UniswapV4 mint/increase/decrease liquidity support (#189)
deluca-mike Nov 25, 2025
01094c8
feat: UniswapV4 swap support (SC-1249) (#194)
deluca-mike Nov 26, 2025
a97565b
feat: Add `ALM_PROXY` and `RATE_LIMITS` addresses to deploy json (#190)
supercontracts Nov 28, 2025
3e9be4e
fix: Uniswap V4 USDT/USDS tests and swap fix (SC-1253) (#196)
deluca-mike Nov 30, 2025
29edee5
add: Uniswap fuzz tests (SC-1250) (#195)
supercontracts Dec 3, 2025
c948112
feat: Apply Tick Checks on `increaseLiquidityUniswapV4` (SC-1262) (#197)
deluca-mike Dec 9, 2025
235186f
feat: UniswapV4 swap invalid tokenIn clear error (SC-1259) (#199)
deluca-mike Dec 10, 2025
9ef81e8
feat: Remove shadow declaration warning and add zero address check (S…
lucas-manuel Dec 11, 2025
9441a7e
feat: Update ForeignController to use modifiers (SC-1281) (#201)
supercontracts Dec 17, 2025
c1e80c4
cleanup: Remove Morpho Allocations (#202)
supercontracts Dec 18, 2025
61cbc57
fix: Inconsistent Uniswap v4 PositionManager action usage in increase…
supercontracts Dec 18, 2025
7b59cf5
fix: Remove unused `amount0Min`/`amount1Min` from `_decreaseLiquidity…
supercontracts Dec 19, 2025
3fe3072
fix: Minor issues, Typos & Documentation C11 (#206)
supercontracts Dec 19, 2025
f8e16e4
fix: Reuse `_rateLimited` to save space (#209)
deluca-mike Dec 19, 2025
3d2d0dd
fix: _requirePoolIdMatch in mint and swap C10 (#211)
deluca-mike Dec 19, 2025
d54b80d
fix: Add an explicit bounds check to prevent silent truncation in `_a…
supercontracts Dec 19, 2025
3dbc7cb
feat: Add additional requirements/assumptions (C-22) (#212)
lucas-manuel Dec 22, 2025
16525ad
feat: Add 1.9.0 audits (#213)
lucas-manuel Jan 6, 2026
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/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- name: Build contracts
run: |
forge --version
forge build --skip test --sizes
forge build --sizes ./src

test:
runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,9 @@
path = lib/spark-vaults-v2
url = https://github.com/sparkdotfi/spark-vaults-v2
branch = dev
[submodule "lib/uniswap-v4-periphery"]
path = lib/uniswap-v4-periphery
url = https://github.com/Uniswap/v4-periphery.git
[submodule "lib/uniswap-v4-core"]
path = lib/uniswap-v4-core
url = https://github.com/Uniswap/v4-core.git
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,12 +140,14 @@ Below are all stated trust assumptions for using this contract in production:
- Assume that the funds return to the OTC Buffer contract via transfer. This is to accommodate most exchanges/OTC desks that only have the ability to complete the swap by sending token to an address (i.e. not being able to make any arbitrary contracts calls outside of the ERC20 spec).
- The maximum loss by the protocol is limited to the single outstanding OTC swap amount for a given exchange.
- The recharge rate is configured to be low enough that the system will not practically allow for multiple swaps in a row without receiving material funds from the exchange.
- Ethena's delegated signer role can be set by the RELAYER. The delegated signer role can technically be set by a malicious relayer to be a malicious actor. Ethena's API's [Order Validity Checks](https://docs.ethena.fi/solution-design/minting-usde/order-validity-checks) is trusted to prevent attacks in this scenario.

## Operational Requirements

- All ERC-4626 vaults that are onboarded MUST have an initial burned shares amount that prevents rounding-based frontrunning attacks. These shares have to be unrecoverable so that they cannot be removed at a later date.
- All ERC-20 tokens are to be non-rebasing with sufficiently high decimal precision.
- Rate limits must be configured for specific ERC-4626 vaults and AAVE aTokens (vaults without rate limits set will revert). Unlimited rate limits can be used as an onboarding tool.
- All Uniswap V4 pool onboardings are to be done with 1:1 assets.
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, understand the repo structure
find . -type f -name "*.sol" | head -20

Repository: sparkdotfi/spark-alm-controller

Length of output: 701


🏁 Script executed:

#!/bin/bash
# Search for Uniswap V4 onboarding/configuration functions
rg -nP -C5 '(setUniswapV4|addUniswapV4|configureUniswapV4|onboard.*[Uu]niswap.*[Vv]4)' --type=sol

# Also search more broadly for Uniswap V4 related functions
rg -nP 'UniswapV4|uniswapv4' --type=sol -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 130


🏁 Script executed:

#!/bin/bash
# Search for any validation logic checking asset parity, 1:1 ratios, or equal assets
rg -nP -C3 '(require.*1:1|require.*equal.*asset|require.*parity|asset.*ratio|token.*token|decimal)' --type=sol | head -100

Repository: sparkdotfi/spark-alm-controller

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Search for Uniswap V4 related code in all files
rg -nP '(UniswapV4|uniswapv4)' -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 33697


🏁 Script executed:

#!/bin/bash
# Search for 1:1 asset or equal token validation
rg -nP -C3 '(1:1|equal.*token|token.*equal|require.*asset)' | head -150

Repository: sparkdotfi/spark-alm-controller

Length of output: 11041


🏁 Script executed:

#!/bin/bash
# Look at the README to see what it says about the 1:1 constraint
rg -nP -C5 '1:1.*asset|Uniswap.*V4.*onboard' -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 1006


🏁 Script executed:

#!/bin/bash
# Read UniswapV4Lib.sol to understand the implementation
wc -l src/libraries/UniswapV4Lib.sol
head -100 src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 4025


🏁 Script executed:

#!/bin/bash
# Read the setUniswapV4TickLimits function
sed -n '323,350p' src/MainnetController.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 1129


🏁 Script executed:

#!/bin/bash
# Search for any validation that checks if assets are equal or 1:1 in Uniswap V4
rg -n 'token0|token1|asset' src/libraries/UniswapV4Lib.sol -A 3 -B 1 | head -80

Repository: sparkdotfi/spark-alm-controller

Length of output: 3283


🏁 Script executed:

#!/bin/bash
# Look for _getPoolKeyFromPoolId and _requirePoolIdMatch to see if there's validation
grep -n '_getPoolKeyFromPoolId\|_requirePoolIdMatch' src/libraries/UniswapV4Lib.sol -A 5

Repository: sparkdotfi/spark-alm-controller

Length of output: 1894


🏁 Script executed:

#!/bin/bash
# Search for any function that validates or registers Uniswap V4 pools
grep -n 'require.*currency\|require.*token\|pool.*register\|pool.*onboard' src/libraries/UniswapV4Lib.sol -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 167


🏁 Script executed:

#!/bin/bash
# Check the full implementation of critical validation functions
sed -n '450,548p' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 3408


🏁 Script executed:

#!/bin/bash
# Search for any pool whitelist or registration mechanism
rg -n 'whitelist.*pool\|pool.*register\|pool.*onboard' src/ -i -A 2 -B 2

Repository: sparkdotfi/spark-alm-controller

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check if there are any restrictions on what pools can be used
grep -n 'poolId\|PoolKey' src/MainnetController.sol | head -30

Repository: sparkdotfi/spark-alm-controller

Length of output: 752


🏁 Script executed:

#!/bin/bash
# Look for any other setups or constraints in the test files about 1:1 assets
rg -n '1:1|equal.*asset|token.*token|currency.*currency' test/mainnet-fork/Uniswapv4.t.sol | head -20

Repository: sparkdotfi/spark-alm-controller

Length of output: 1212


🏁 Script executed:

#!/bin/bash
# Let's check if there's any validation when setting tick limits or creating pools
# Look at the full setUniswapV4TickLimits function and surrounding context
sed -n '320,350p' src/MainnetController.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 1147


🏁 Script executed:

#!/bin/bash
# Check if poolId can be set arbitrarily or if there's a whitelist
rg -n 'poolId.*require\|whitelist.*poolId\|validate.*pool' src/ --type=sol -i

Repository: sparkdotfi/spark-alm-controller

Length of output: 102


🏁 Script executed:

#!/bin/bash
# Look at _getNormalizedBalance and understand the rate limit calculation
sed -n '500,510p' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 463


🏁 Script executed:

#!/bin/bash
# Let's search more carefully for any asset validation or 1:1 checks
rg -n 'currency0.*currency1\|token0.*token1' src/libraries/UniswapV4Lib.sol -B 2 -A 2

Repository: sparkdotfi/spark-alm-controller

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check if there's any admin function that validates pools before they're used
rg -n 'onboard\|register\|add.*pool\|setup.*pool' src/ -i -A 3

Repository: sparkdotfi/spark-alm-controller

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Let's look at the full _increaseLiquidity function to understand rate limit calculation
sed -n '298,350p' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 2166


🏁 Script executed:

#!/bin/bash
# Let's look at the swap function to see how rate limits work there
sed -n '170,200p' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 1081


🏁 Script executed:

#!/bin/bash
# Check if there's any validation when a pool is first configured
# Look for any function that takes poolId and validates assets
grep -n 'function.*poolId\|function.*pool' src/MainnetController.sol | head -20

Repository: sparkdotfi/spark-alm-controller

Length of output: 268


🏁 Script executed:

#!/bin/bash
# Check if there's any place where pools are registered or validated
sed -n '1,50p' src/MainnetController.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 2408


🏁 Script executed:

#!/bin/bash
# Look at how tests set up pools to see if they validate 1:1 assets
head -150 test/mainnet-fork/Uniswapv4.t.sol | tail -50

Repository: sparkdotfi/spark-alm-controller

Length of output: 2440


🏁 Script executed:

#!/bin/bash
# Check the test setup for pool creation
sed -n '100,150p' test/mainnet-fork/Uniswapv4.t.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 2441


🏁 Script executed:

#!/bin/bash
# Let's check if there are any pool validation functions we might have missed
grep -n 'function.*V4\|function.*Uniswap' src/MainnetController.sol | head -20

Repository: sparkdotfi/spark-alm-controller

Length of output: 277


🏁 Script executed:

#!/bin/bash
# One final check - look for any require statements that check token decimals or prices
grep -n 'decimals\|price\|exchange.*rate' src/libraries/UniswapV4Lib.sol

Repository: sparkdotfi/spark-alm-controller

Length of output: 270


Add validation to enforce the 1:1 asset constraint for Uniswap V4 pools.

The 1:1 asset requirement is documented in the README but not enforced in code. The setUniswapV4TickLimits function only validates tick parameters without checking that token0 and token1 are equal-valued assets. While the _increaseLiquidity function includes a comment acknowledging the assumption that tokens are valued equally, no programmatic validation prevents configuring pools with mismatched asset values.

This is critical because the rate limit calculation normalizes both tokens to 18 decimals and sums them without accounting for actual USD values (per the learned design decision about normalized balances). An admin could inadvertently—or maliciously—configure a non-1:1 pool and create a rate limit bypass via price discrepancies.

Consider adding validation in setUniswapV4TickLimits to verify token0 and token1 are equal-valued assets before allowing pool configuration.

🤖 Prompt for AI Agents
In @README.md at line 150, Add programmatic validation in setUniswapV4TickLimits
to enforce the README's 1:1 asset requirement: before accepting tick limits,
verify token0 and token1 represent equal-valued assets (e.g., compare their
canonical asset IDs or use the same oracle/price feed to ensure identical USD
value per normalized unit) and revert with a clear error if they differ; update
any callers/tests accordingly and remove the reliance on the comment in
_increaseLiquidity by making this guarantee upfront in setUniswapV4TickLimits.

- Rate limits must take into account:
- Risk tolerance for a given protocol
- Griefing attacks (e.g., repetitive transactions with high slippage by malicious relayer).
Expand Down
Binary file added audits/v190-cantina-audit.pdf
Binary file not shown.
Binary file added audits/v190-certora-audit.pdf
Binary file not shown.
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ remappings = [
'@layerzerolabs/lz-evm-messagelib-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/messagelib/',
'solidity-bytes-utils/=lib/solidity-bytes-utils/',
'forge-std/=lib/forge-std/src/',
'@uniswap/v4-core/=lib/uniswap-v4-core/'
]

[fuzz]
Expand Down
1 change: 1 addition & 0 deletions lib/uniswap-v4-core
Submodule uniswap-v4-core added at e50237
1 change: 1 addition & 0 deletions lib/uniswap-v4-periphery
Submodule uniswap-v4-periphery added at 377938
4 changes: 3 additions & 1 deletion script/input/1/arbitrum_one-production.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
"freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431",
"usdc": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831",
"usds": "0x6491c05A82219b8D1479057361ff1654749b876b",
"susds": "0xdDb46999F8891663a8F2828d25298f70416d7610"
"susds": "0xdDb46999F8891663a8F2828d25298f70416d7610",
"almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709",
"rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838"
Comment on lines +9 to +11
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Inconsistent field ordering.

The field order is almProxy then rateLimits, but the other production configs (unichain, avalanche, optimism) use rateLimits then almProxy. While JSON objects are unordered, maintaining consistency across similar configuration files improves maintainability.

🔎 Suggested reordering for consistency
     "susds": "0xdDb46999F8891663a8F2828d25298f70416d7610",
-    "almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709",
-    "rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838"
+    "rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838",
+    "almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"susds": "0xdDb46999F8891663a8F2828d25298f70416d7610",
"almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709",
"rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838"
"susds": "0xdDb46999F8891663a8F2828d25298f70416d7610",
"rateLimits": "0x19D08879851FB54C2dCc4bb32b5a1EA5E9Ad6838",
"almProxy": "0x92afd6F2385a90e44da3a8B60fe36f6cBe1D8709"
🤖 Prompt for AI Agents
In @script/input/1/arbitrum_one-production.json around lines 9 - 11, Reorder the
JSON object fields so they match the ordering used in other production configs:
place "rateLimits" before "almProxy" (keeping "susds" as-is); update the entry
that currently lists "almProxy" then "rateLimits" to instead list "rateLimits"
then "almProxy" for consistency across configs.

}
4 changes: 3 additions & 1 deletion script/input/1/avalanche-production.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,7 @@
"psm": "0x0000000000000000000000000000000000000000",
"relayer": "0x8a25A24EDE9482C4Fc0738F99611BE58F1c839AB",
"freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431",
"usdc": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E"
"usdc": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E",
"rateLimits": "0xb79972e8B21f0dE911E65AC342ac85ad38C9A77a",
"almProxy": "0xecE6B0E8a54c2f44e066fBb9234e7157B15b7FeC"
}
4 changes: 3 additions & 1 deletion script/input/1/optimism-production.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
"freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431",
"usdc": "0x0b2C639c533813f4Aa9D7837CAf62653d097Ff85",
"usds": "0x4F13a96EC5C4Cf34e442b46Bbd98a0791F20edC3",
"susds": "0xb5B2dc7fd34C249F4be7fB1fCea07950784229e0"
"susds": "0xb5B2dc7fd34C249F4be7fB1fCea07950784229e0",
"rateLimits": "0x6B34A6B84444dC3Fc692821D5d077a1e4927342d",
"almProxy": "0x876664f0c9Ff24D1aa355Ce9f1680AE1A5bf36fB"
}
4 changes: 3 additions & 1 deletion script/input/1/unichain-production.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
"freezer": "0x90D8c80C028B4C09C0d8dcAab9bbB057F0513431",
"usdc": "0x078D782b760474a361dDA0AF3839290b0EF57AD6",
"usds": "0x7E10036Acc4B56d4dFCa3b77810356CE52313F9C",
"susds": "0xA06b10Db9F390990364A3984C04FaDf1c13691b5"
"susds": "0xA06b10Db9F390990364A3984C04FaDf1c13691b5",
"rateLimits": "0x5A1a44D2192Dd1e21efB9caA50E32D0716b35535",
"almProxy": "0x345E368fcCd62266B3f5F37C9a131FD1c39f5869"
}
76 changes: 18 additions & 58 deletions src/ForeignController.sol
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,8 @@ contract ForeignController is ReentrancyGuard, AccessControlEnumerable {
}

function setMaxExchangeRate(address token, uint256 shares, uint256 maxExpectedAssets)
external nonReentrant
external nonReentrant onlyRole(DEFAULT_ADMIN_ROLE)
{
_checkRole(DEFAULT_ADMIN_ROLE);

require(token != address(0), "FC/token-zero-address");

emit MaxExchangeRateSet(
Expand All @@ -187,13 +185,14 @@ contract ForeignController is ReentrancyGuard, AccessControlEnumerable {
/**********************************************************************************************/

function transferAsset(address asset, address destination, uint256 amount)
external nonReentrant onlyRole(RELAYER)
{
_rateLimited(
external
nonReentrant
onlyRole(RELAYER)
rateLimited(
RateLimitHelpers.makeAddressAddressKey(LIMIT_ASSET_TRANSFER, asset, destination),
amount
);

)
{
bytes memory returnData = proxy.doCall(
asset,
abi.encodeCall(IERC20(asset).transfer, (destination, amount))
Expand Down Expand Up @@ -298,13 +297,18 @@ contract ForeignController is ReentrancyGuard, AccessControlEnumerable {
uint256 amount,
uint32 destinationEndpointId
)
external payable nonReentrant
{
_checkRole(RELAYER);
_rateLimited(
external
payable
nonReentrant
onlyRole(RELAYER)
rateLimited(
keccak256(abi.encode(LIMIT_LAYERZERO_TRANSFER, oftAddress, destinationEndpointId)),
amount
);
)
{
bytes32 recipient = layerZeroRecipients[destinationEndpointId];

require(recipient != bytes32(0), "FC/recipient-not-set");

// NOTE: Full integration testing of this logic is not possible without OFTs with
// approvalRequired == true. Add integration testing for this case before
Expand All @@ -317,7 +321,7 @@ contract ForeignController is ReentrancyGuard, AccessControlEnumerable {

SendParam memory sendParams = SendParam({
dstEid : destinationEndpointId,
to : layerZeroRecipients[destinationEndpointId],
to : recipient,
amountLD : amount,
minAmountLD : 0,
extraOptions : options,
Expand Down Expand Up @@ -478,46 +482,6 @@ contract ForeignController is ReentrancyGuard, AccessControlEnumerable {
);
}

/**********************************************************************************************/
/*** Relayer Morpho functions ***/
/**********************************************************************************************/

function setSupplyQueueMorpho(address morphoVault, Id[] memory newSupplyQueue)
external
nonReentrant
onlyRole(RELAYER)
rateLimitExists(RateLimitHelpers.makeAddressKey(LIMIT_4626_DEPOSIT, morphoVault))
{
proxy.doCall(
morphoVault,
abi.encodeCall(IMetaMorpho(morphoVault).setSupplyQueue, (newSupplyQueue))
);
}

function updateWithdrawQueueMorpho(address morphoVault, uint256[] calldata indexes)
external
nonReentrant
onlyRole(RELAYER)
rateLimitExists(RateLimitHelpers.makeAddressKey(LIMIT_4626_DEPOSIT, morphoVault))
{
proxy.doCall(
morphoVault,
abi.encodeCall(IMetaMorpho(morphoVault).updateWithdrawQueue, (indexes))
);
}

function reallocateMorpho(address morphoVault, MarketAllocation[] calldata allocations)
external
nonReentrant
onlyRole(RELAYER)
rateLimitExists(RateLimitHelpers.makeAddressKey(LIMIT_4626_DEPOSIT, morphoVault))
{
proxy.doCall(
morphoVault,
abi.encodeCall(IMetaMorpho(morphoVault).reallocate, (allocations))
);
}

/**********************************************************************************************/
/*** Spark Vault functions ***/
/**********************************************************************************************/
Expand Down Expand Up @@ -597,10 +561,6 @@ contract ForeignController is ReentrancyGuard, AccessControlEnumerable {
emit CCTPTransferInitiated(nonce, destinationDomain, mintRecipient, usdcAmount);
}

function _rateLimited(bytes32 key, uint256 amount) internal {
rateLimits.triggerRateLimitDecrease(key, amount);
}

/**********************************************************************************************/
/*** Exchange rate helper functions ***/
/**********************************************************************************************/
Expand Down
Loading
Loading