From 1f2fb58ea51c43ac7e10a9055b1ad83d0979b181 Mon Sep 17 00:00:00 2001 From: Hrishabh Ayush Date: Fri, 7 Feb 2025 16:39:57 -0500 Subject: [PATCH 1/9] All files added to the prototypes --- .gitmodules | 8 +- musical-charts/.gitignore | 14 ++ musical-charts/README.md | 64 ++++++ musical-charts/foundry.toml | 6 + musical-charts/lib/forge-std | 1 + musical-charts/lib/solmate | 1 + musical-charts/script/ViolinAMM.s.sol | 25 +++ musical-charts/src/SRC20.sol | 103 ++++++++++ musical-charts/src/ViolinAMM.sol | 209 ++++++++++++++++++++ musical-charts/src/ViolinCoin.sol | 51 +++++ musical-charts/test/ViolinAMM.t.sol | 269 ++++++++++++++++++++++++++ 11 files changed, 750 insertions(+), 1 deletion(-) create mode 100644 musical-charts/.gitignore create mode 100644 musical-charts/README.md create mode 100644 musical-charts/foundry.toml create mode 160000 musical-charts/lib/forge-std create mode 160000 musical-charts/lib/solmate create mode 100644 musical-charts/script/ViolinAMM.s.sol create mode 100644 musical-charts/src/SRC20.sol create mode 100644 musical-charts/src/ViolinAMM.sol create mode 100644 musical-charts/src/ViolinCoin.sol create mode 100644 musical-charts/test/ViolinAMM.t.sol diff --git a/.gitmodules b/.gitmodules index 45fe273..f846cea 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "walnut/lib/forge-std"] path = walnut/lib/forge-std - url = https://github.com/foundry-rs/forge-std \ No newline at end of file + url = https://github.com/foundry-rs/forge-std +[submodule "musical-charts/lib/forge-std"] + path = musical-charts/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "musical-charts/lib/solmate"] + path = musical-charts/lib/solmate + url = https://github.com/Rari-Capital/solmate diff --git a/musical-charts/.gitignore b/musical-charts/.gitignore new file mode 100644 index 0000000..85198aa --- /dev/null +++ b/musical-charts/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/musical-charts/README.md b/musical-charts/README.md new file mode 100644 index 0000000..9b2cfac --- /dev/null +++ b/musical-charts/README.md @@ -0,0 +1,64 @@ +# Musical Charts + +## Overview +**Musical Charts** work on creating an Automated Market Maker (AMM) where people can swap tokens only after listening to music. Users experience market trends through AI generated violin music, and they are allowed to make a trade signal after they have listened to the generated music. The project is more fun because each trade triggers a new violin melody based on the market data, ensuring a fresh and engaging experience every time for the user. + +## Problem +Price data of any token or memecoin alone is not engaging to a general user and often user doesn't feel the thrill or enjoyment while trading these coins. + +## Insight +Restricting financial signals of buy/sell through music can foster a more intuitive and emotional connection to market trends. + +## Solution +Encrypt and process real-time price data in a *Trusted Execution Environment* (TEE) to generate violin compositions, allowing users to ‘listen’ to market changes before they swap. + +## Goals +Focus on engagement and excitement rather than commercial viability, providing a unique, shareable experience. + +## Get Involved +Join us in redefining data engagement and creating a fun trading experience through sound! + +## Usage + +### Build + +```shell +$ sforge build +``` + +### Test + +```shell +$ sforge test +``` + +### Format + +```shell +$ sforge fmt +``` + +### Gas Snapshots + +```shell +$ sforge snapshot +``` + +### Anvil + +```shell +$ sanvil +``` + +### Deploy + +```shell +$ sforge script script/ViolinAMM.s.sol:ViolinAMMScript --rpc-url --private-key +``` + +### Help + +```shell +$ sforge --help +$ sanvil --help +``` diff --git a/musical-charts/foundry.toml b/musical-charts/foundry.toml new file mode 100644 index 0000000..25b918f --- /dev/null +++ b/musical-charts/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/musical-charts/lib/forge-std b/musical-charts/lib/forge-std new file mode 160000 index 0000000..3b20d60 --- /dev/null +++ b/musical-charts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/musical-charts/lib/solmate b/musical-charts/lib/solmate new file mode 160000 index 0000000..c93f771 --- /dev/null +++ b/musical-charts/lib/solmate @@ -0,0 +1 @@ +Subproject commit c93f7716c9909175d45f6ef80a34a650e2d24e56 diff --git a/musical-charts/script/ViolinAMM.s.sol b/musical-charts/script/ViolinAMM.s.sol new file mode 100644 index 0000000..047dd95 --- /dev/null +++ b/musical-charts/script/ViolinAMM.s.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console} from "forge-std/Script.sol"; +import {ViolinAMM} from "../src/ViolinAMM.sol"; +import {ViolinCoin} from "../src/ViolinCoin.sol"; + +contract ViolinAMMScript is Script { + ViolinAMM public amm; + ViolinCoin public baseAsset; + ViolinCoin public quoteAsset; + + function setUp() public { + address admin = address(0x14dC79964da2C08b23698B3D3cc7Ca32193d9955); + baseAsset = new ViolinCoin(admin, "Base Asset", "BASE", 18); + quoteAsset = new ViolinCoin(admin, "Quote Asset", "QUOTE", 18); + } + + function run() public { + vm.startBroadcast(); + address admin = address(0x14dC79964da2C08b23698B3D3cc7Ca32193d9955); + amm = new ViolinAMM(baseAsset, quoteAsset, 1e18, 1e18, admin); + vm.stopBroadcast(); + } +} diff --git a/musical-charts/src/SRC20.sol b/musical-charts/src/SRC20.sol new file mode 100644 index 0000000..8d2c77f --- /dev/null +++ b/musical-charts/src/SRC20.sol @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.13; + +/*////////////////////////////////////////////////////////////// +// ISRC20 Interface +//////////////////////////////////////////////////////////////*/ + +interface ISRC20 { + /*////////////////////////////////////////////////////////////// + METADATA FUNCTIONS + //////////////////////////////////////////////////////////////*/ + function name() external view returns (string memory); + function symbol() external view returns (string memory); + function decimals() external view returns (uint8); + + /*////////////////////////////////////////////////////////////// + ERC20 FUNCTIONS + //////////////////////////////////////////////////////////////*/ + function balanceOf() external view returns (uint256); + function approve(saddress spender, suint256 amount) external returns (bool); + function transfer(saddress to, suint256 amount) external returns (bool); + function transferFrom(saddress from, saddress to, suint256 amount) external returns (bool); +} + +/*////////////////////////////////////////////////////////////// +// SRC20 Contract +//////////////////////////////////////////////////////////////*/ + +abstract contract SRC20 is ISRC20 { + /*////////////////////////////////////////////////////////////// + METADATA STORAGE + //////////////////////////////////////////////////////////////*/ + string public name; + string public symbol; + uint8 public immutable decimals; + + /*////////////////////////////////////////////////////////////// + ERC20 STORAGE + //////////////////////////////////////////////////////////////*/ + // All storage variables that will be mutated must be confidential to + // preserve functional privacy. + suint256 internal totalSupply; + mapping(saddress => suint256) internal balance; + mapping(saddress => mapping(saddress => suint256)) internal allowance; + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + constructor(string memory _name, string memory _symbol, uint8 _decimals) { + name = _name; + symbol = _symbol; + decimals = _decimals; + } + + /*////////////////////////////////////////////////////////////// + ERC20 LOGIC + //////////////////////////////////////////////////////////////*/ + function balanceOf() public view virtual returns (uint256) { + return uint256(balance[saddress(msg.sender)]); + } + + function approve(saddress spender, suint256 amount) public virtual returns (bool) { + allowance[saddress(msg.sender)][spender] = amount; + return true; + } + + function transfer(saddress to, suint256 amount) public virtual returns (bool) { + // msg.sender is public information, casting to saddress below doesn't change this + balance[saddress(msg.sender)] -= amount; + unchecked { + balance[to] += amount; + } + return true; + } + + function transferFrom(saddress from, saddress to, suint256 amount) public virtual returns (bool) { + suint256 allowed = allowance[from][saddress(msg.sender)]; // Saves gas for limited approvals. + if (allowed != suint256(type(uint256).max)) { + allowance[from][saddress(msg.sender)] = allowed - amount; + } + + balance[from] -= amount; + unchecked { + balance[to] += amount; + } + return true; + } + + /*////////////////////////////////////////////////////////////// + INTERNAL MINT/BURN LOGIC + //////////////////////////////////////////////////////////////*/ + function _mint(saddress to, suint256 amount) internal virtual { + totalSupply += amount; + unchecked { + balance[to] += amount; + } + } + + function _burn(saddress to, suint256 amount) internal virtual { + totalSupply -= amount; + balance[to] -= amount; + } +} diff --git a/musical-charts/src/ViolinAMM.sol b/musical-charts/src/ViolinAMM.sol new file mode 100644 index 0000000..6b354ef --- /dev/null +++ b/musical-charts/src/ViolinAMM.sol @@ -0,0 +1,209 @@ +/* + * SPDX-License-Identifier: UNLICENSED + * + * AMM that hides the price of quote asset until it's above some threshold. + * + */ +pragma solidity ^0.8.13; + +import "solmate/tokens/ERC20.sol"; +import "solmate/utils/FixedPointMathLib.sol"; +import "solmate/utils/ReentrancyGuard.sol"; + +import "./ViolinCoin.sol"; + +/*////////////////////////////////////////////////////////////// +// ViolinAMM Contract +//////////////////////////////////////////////////////////////*/ + +contract ViolinAMM is ReentrancyGuard { + /*////////////////////////////////////////////////////////////// + // TOKEN STORAGE + //////////////////////////////////////////////////////////////*/ + ViolinCoin public baseAsset; + ViolinCoin public quoteAsset; + + /*////////////////////////////////////////////////////////////// + // AMM STORAGE + //////////////////////////////////////////////////////////////*/ + saddress adminAddress; + + suint256 wad; + suint256 priceReveal; + + suint256 baseReserve; + suint256 quoteReserve; + + mapping(saddress => sbool) hasListened; + mapping(saddress => suint256) lastListenedTimestamp; + + /*////////////////////////////////////////////////////////////// + // EVENTS + //////////////////////////////////////////////////////////////*/ + + // Listening event should be an encrypted event + /// @notice Emitted when a user puts a request to listen + event Listening(address indexed user); + + /// @notice Emitted when a swap is executed by the user + event SwapExecuted(address indexed user); + + /*////////////////////////////////////////////////////////////// + // MODIFIERS + //////////////////////////////////////////////////////////////*/ + + /* + * Only listener can call this function + */ + modifier onlyViolinListener() { + require(hasListened[saddress(msg.sender)], "You are not the listener"); + _; + } + + /* + * Listen to the music + */ + function listen() external { + hasListened[saddress(msg.sender)] = sbool(true); + lastListenedTimestamp[saddress(msg.sender)] = suint256(block.timestamp); + emit Listening(msg.sender); + } + + /*////////////////////////////////////////////////////////////// + CONSTRUCTOR + //////////////////////////////////////////////////////////////*/ + constructor( + ViolinCoin _baseAsset, + ViolinCoin _quoteAsset, + uint256 _wad, + uint256 _priceReveal, + address _adminAddress + ) { + baseAsset = _baseAsset; + quoteAsset = _quoteAsset; + + adminAddress = saddress(_adminAddress); + + // Stored as suint256 for convenience. Not actually shielded bc it's a + // transparent parameter in the constructor + wad = suint256(_wad); + priceReveal = suint256(_priceReveal); + } + /*////////////////////////////////////////////////////////////// + AMM LOGIC + //////////////////////////////////////////////////////////////*/ + + /* + * Add liquidity to pool. No LP rewards in this implementation. + */ + function addLiquidity(suint256 baseAmount, suint256 quoteAmount) external { + baseReserve = baseReserve + baseAmount; + quoteReserve = quoteReserve + quoteAmount; + + saddress ssender = saddress(msg.sender); + saddress sthis = saddress(address(this)); + baseAsset.transferFrom(ssender, sthis, baseAmount); + quoteAsset.transferFrom(ssender, sthis, quoteAmount); + } + + /* + * Wrapper around swap so calldata for trade looks the same regardless of + * direction. + */ + function swap(suint256 baseIn, suint256 quoteIn) public nonReentrant onlyViolinListener { + // After listening to the music, the user can call this function to swap the assets + // Price gets revealed here + require( + suint256(block.timestamp) >= suint256(lastListenedTimestamp[saddress(msg.sender)]) + suint256(10 seconds), + "Must wait 10 seconds before calling swap" + ); + + suint256 baseOut; + suint256 quoteOut; + + (baseOut, baseReserve, quoteReserve) = _swap(baseAsset, quoteAsset, baseReserve, quoteReserve, baseIn); + (quoteOut, quoteReserve, baseReserve) = _swap(quoteAsset, baseAsset, quoteReserve, baseReserve, quoteIn); + + emit SwapExecuted(msg.sender); + hasListened[saddress(msg.sender)] = sbool(false); + } + + /* + * Swap for cfAMM. No fees. + */ + function _swap(ViolinCoin tokenIn, ViolinCoin tokenOut, suint256 reserveIn, suint256 reserveOut, suint256 amountIn) + internal + returns (suint256 amountOut, suint256 reserveInNew, suint256 reserveOutNew) + { + suint256 numerator = mulDivDown(reserveOut, amountIn, wad); + suint256 denominator = reserveIn + amountIn; + amountOut = mulDivDown(numerator, wad, denominator); + + reserveInNew = reserveIn + amountIn; + reserveOutNew = reserveOut - amountOut; + + saddress ssender = saddress(msg.sender); + saddress sthis = saddress(address(this)); + tokenIn.transferFrom(ssender, sthis, amountIn); + tokenOut.transfer(ssender, amountOut); + } + + /* + * Only returns price if it's above priceReveal threshold. + */ + function getPriceGated() external view requirePriceSufficient returns (uint256 price) { + return uint256(_computePrice()); + } + + /* + * Bypasses priceReveal threshold. For testing purposes. + */ + function getPrice() external onlyViolinListener returns (uint256 price) { + hasListened[saddress(msg.sender)] = sbool(false); + return uint256(_computePrice()); + } + + /* + * Compute price of quote asset. + */ + function _computePrice() internal view returns (suint256 price) { + price = mulDivDown(baseReserve, wad, quoteReserve); + } + + /* + * For wad math. + */ + function mulDivDown(suint256 x, suint256 y, suint256 denominator) internal pure returns (suint256 z) { + require( + denominator != suint256(0) && (y == suint256(0) || x <= suint256(type(uint256).max) / y), + "Overflow or division by zero" + ); + z = (x * y) / denominator; + } + + /* + * Assert price of quote asset above priceReveal threshold. + */ + modifier requirePriceSufficient() { + require(_computePrice() >= priceReveal, "Price of quote asset is not high enough."); + _; + } + + /*////////////////////////////////////////////////////////////// + // GETTERS + //////////////////////////////////////////////////////////////*/ + + /* + * Get the base reserve + */ + function getBaseReserve() external view returns (uint256) { + return uint256(baseReserve); + } + + /* + * Get the quote reserve + */ + function getQuoteReserve() external view returns (uint256) { + return uint256(quoteReserve); + } +} diff --git a/musical-charts/src/ViolinCoin.sol b/musical-charts/src/ViolinCoin.sol new file mode 100644 index 0000000..678e8e6 --- /dev/null +++ b/musical-charts/src/ViolinCoin.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.13; + +import {SRC20, ISRC20} from "./SRC20.sol"; + +/*////////////////////////////////////////////////////////////// +// IViolinCoin Interface +//////////////////////////////////////////////////////////////*/ + +// IViolinCoin extends ISRC20 by adding the mint function. +interface IViolinCoin is ISRC20 { + function mint(saddress to, suint256 amount) external; +} +/*////////////////////////////////////////////////////////////// +// ViolinCoin Contract +//////////////////////////////////////////////////////////////*/ + +contract ViolinCoin is SRC20, IViolinCoin { + address public owner; + + constructor(address _owner, string memory _name, string memory _symbol, uint8 _decimals) + SRC20(_name, _symbol, _decimals) + { + owner = _owner; + } + + modifier onlyOwner() { + require(msg.sender == owner, "Must be owner"); + _; + } + + /// @notice Mints new tokens to the specified address. + function mint(saddress to, suint256 amount) public onlyOwner { + _mint(to, amount); + } + + /// @notice Returns the balance of msg.sender. + function balanceOf() public view override(ISRC20, SRC20) returns (uint256) { + return SRC20.balanceOf(); + } + + /// @notice Transfers tokens to another address. + function transfer(saddress to, suint256 amount) public override(ISRC20, SRC20) returns (bool) { + return SRC20.transfer(to, amount); + } + + /// @notice Transfers tokens from one address to another. + function transferFrom(saddress from, saddress to, suint256 amount) public override(ISRC20, SRC20) returns (bool) { + return SRC20.transferFrom(from, to, amount); + } +} diff --git a/musical-charts/test/ViolinAMM.t.sol b/musical-charts/test/ViolinAMM.t.sol new file mode 100644 index 0000000..5565d21 --- /dev/null +++ b/musical-charts/test/ViolinAMM.t.sol @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "solmate/tokens/ERC20.sol"; +import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; + +import "../src/ViolinAMM.sol"; +import {Test, console} from "forge-std/Test.sol"; + +/*////////////////////////////////////////////////////////////// +// ViolinAMMTest Contract +//////////////////////////////////////////////////////////////*/ +contract ViolinAMMTest is Test { + /*////////////////////////////////////////////////////////////// + // AMM STORAGE + //////////////////////////////////////////////////////////////*/ + ViolinAMM public amm; + + /*////////////////////////////////////////////////////////////// + // TOKEN STORAGE + //////////////////////////////////////////////////////////////*/ + ViolinCoin baseAsset; + ViolinCoin quoteAsset; + + /*////////////////////////////////////////////////////////////// + // AMM STORAGE + //////////////////////////////////////////////////////////////*/ + address testAdmin = address(0xabcd); + + uint256 constant WAD = 1e18; + uint8 constant WAD_ZEROS = 18; + + address constant SWAPPER1_ADDR = address(123); + address constant SWAPPER2_ADDR = address(456); + + address constant NON_LISTENER_ADDR = address(789); + + /*////////////////////////////////////////////////////////////// + // SETUP + //////////////////////////////////////////////////////////////*/ + function setUp() public { + baseAsset = new ViolinCoin(address(this), "Circle", "USDC", 18); + quoteAsset = new ViolinCoin(address(this), "Chainlink", "LINK", 18); + + // Start with pool price 1 LINK = 25 USDC + amm = new ViolinAMM(ViolinCoin(address(baseAsset)), ViolinCoin(address(quoteAsset)), WAD, 25 * WAD, testAdmin); + baseAsset.mint(saddress(address(this)), suint256(200000 * WAD)); + quoteAsset.mint(saddress(address(this)), suint256(10000 * WAD)); + baseAsset.approve(saddress(address(amm)), suint256(200000 * WAD)); + quoteAsset.approve(saddress(address(amm)), suint256(10000 * WAD)); + amm.addLiquidity(suint256(200000 * WAD), suint256(10000 * WAD)); + + // Two swappers start with 50k units of each, LINK and USDC + baseAsset.mint(saddress(SWAPPER1_ADDR), suint256(50000 * WAD)); + quoteAsset.mint(saddress(SWAPPER1_ADDR), suint256(50000 * WAD)); + baseAsset.mint(saddress(SWAPPER2_ADDR), suint256(50000 * WAD)); + quoteAsset.mint(saddress(SWAPPER2_ADDR), suint256(50000 * WAD)); + + // Another address that starts with 50k units of each, LINK and USDC + baseAsset.mint(saddress(NON_LISTENER_ADDR), suint256(50000 * WAD)); + quoteAsset.mint(saddress(NON_LISTENER_ADDR), suint256(50000 * WAD)); + + // Request violin access for test accounts + vm.startPrank(SWAPPER1_ADDR); + amm.listen(); + vm.stopPrank(); + + vm.startPrank(SWAPPER2_ADDR); + amm.listen(); + vm.stopPrank(); + } + + /*////////////////////////////////////////////////////////////// + // TEST CASES + //////////////////////////////////////////////////////////////*/ + /* + * Test case for price going up after swap + */ + function test_PriceUp() public { + vm.startPrank(SWAPPER1_ADDR); + amm.listen(); + vm.warp(block.timestamp + 11); + + uint256 priceT0 = amm.getPrice(); + uint256 swapperBaseT0 = baseAsset.balanceOf(); + uint256 swapperQuoteT0 = quoteAsset.balanceOf(); + + baseAsset.approve(saddress(address(amm)), suint256(30000 * WAD)); + amm.listen(); + vm.warp(block.timestamp + 11); + amm.swap(suint256(30000 * WAD), suint256(0)); + + amm.listen(); + vm.warp(block.timestamp + 11); + assertLt(priceT0, amm.getPrice()); + assertGt(swapperBaseT0, baseAsset.balanceOf()); + assertLt(swapperQuoteT0, quoteAsset.balanceOf()); + + vm.stopPrank(); + } + + /* + * Test case for price going down after swap. + */ + function test_PriceNetDown() public { + vm.startPrank(SWAPPER1_ADDR); + vm.warp(block.timestamp + 11); + uint256 priceT0 = amm.getPrice(); + baseAsset.approve(saddress(address(amm)), suint256(5000 * WAD)); + amm.listen(); + vm.warp(block.timestamp + 11); + amm.swap(suint256(5000 * WAD), suint256(0)); + vm.stopPrank(); + + vm.startPrank(SWAPPER2_ADDR); + vm.warp(block.timestamp + 11); + quoteAsset.approve(saddress(address(amm)), suint256(5000 * WAD)); + amm.swap(suint256(0), suint256(5000 * WAD)); + + amm.listen(); + vm.warp(block.timestamp + 11); + assertGt(priceT0, amm.getPrice()); + + vm.stopPrank(); + } + + /* + * Test case for revealing price. If the price is not above the threshold, + * getPriceGated should revert. + */ + function test_PriceReveal() public { + // Shouldn't see price when 1 LINK = 20 USDC + vm.expectRevert(); + amm.getPriceGated(); + + // Should see price when 1 LINK = 31 USDC after this swap + vm.startPrank(SWAPPER1_ADDR); + vm.warp(block.timestamp + 11); + baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); + amm.swap(suint256(50000 * WAD), suint256(0)); + vm.stopPrank(); + amm.getPriceGated(); + } + + /* + * Test case for swap timing. If the user attempts to swap too quickly, + * the swap should revert. + */ + function test_SwapTiming() public { + vm.startPrank(SWAPPER1_ADDR); + + baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); + + // Immediately attempt another swap + // Should revert due to timing restriction + vm.expectRevert("Must wait 10 seconds before calling swap"); + amm.swap(suint256(50000 * WAD), suint256(0)); + + // Wait 10 seconds and try again + vm.warp(block.timestamp + 11); + amm.swap(suint256(50000 * WAD), suint256(0)); + vm.stopPrank(); + } + + /* + * Test case for access control. If the user is not a listener, they should + * not be able to call swap or getPriceGated. + */ + function test_Access() public { + // Non-listener should not be able to call swap + vm.startPrank(NON_LISTENER_ADDR); + vm.expectRevert("You are not the listener"); + amm.swap(suint256(50000 * WAD), suint256(0)); + vm.stopPrank(); + + // Unauthorized call to getPriceGated should revert + vm.startPrank(NON_LISTENER_ADDR); + vm.expectRevert(); + amm.getPriceGated(); + vm.stopPrank(); + + // After the address gains listener status, they can call swap + vm.startPrank(NON_LISTENER_ADDR); + baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); + amm.listen(); + vm.warp(block.timestamp + 11); + amm.swap(suint256(50000 * WAD), suint256(0)); + amm.getPriceGated(); + vm.stopPrank(); + } + + /* + * Test case for zero swap. If the user attempts to swap zero of both assets, + * then there is no change in the price. + */ + function test_ZeroSwap() public { + vm.startPrank(SWAPPER1_ADDR); + amm.listen(); + vm.warp(block.timestamp + 11); + uint256 priceT0 = amm.getPrice(); + baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); + amm.listen(); + vm.warp(block.timestamp + 11); + amm.swap(suint256(0), suint256(0)); + vm.stopPrank(); + + vm.startPrank(SWAPPER2_ADDR); + amm.listen(); + vm.warp(block.timestamp + 11); + quoteAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); + amm.swap(suint256(0), suint256(0)); + + amm.listen(); + vm.warp(block.timestamp + 11); + assertEq(priceT0, amm.getPrice()); + vm.stopPrank(); + } + + /* + * Test case for liquidity invariance. If two different listeners perform + * swaps, the price should remain almost the same with some level of rounding + * error. + */ + function test_LiquidityInvariance() public { + uint256 baseBefore = amm.getBaseReserve(); + uint256 quoteBefore = amm.getQuoteReserve(); + + uint256 invariantBefore = baseBefore * quoteBefore; + + // Have two different listeners perform swaps + vm.startPrank(SWAPPER1_ADDR); + vm.warp(block.timestamp + 11); + baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); + amm.swap(suint256(500 * WAD), suint256(0)); + vm.stopPrank(); + + uint256 baseAfterSwp1 = amm.getBaseReserve(); + uint256 quoteAfterSwp1 = amm.getQuoteReserve(); + + uint256 invariantAfterSwp1 = baseAfterSwp1 * quoteAfterSwp1; + + vm.startPrank(SWAPPER2_ADDR); + vm.warp(block.timestamp + 11); + baseAsset.approve(saddress(address(amm)), suint256(20000 * WAD)); + amm.swap(suint256(200 * WAD), suint256(0)); + vm.stopPrank(); + + uint256 baseAfterSwp2 = amm.getBaseReserve(); + uint256 quoteAfterSwp2 = amm.getQuoteReserve(); + uint256 invariantAfterSwp2 = baseAfterSwp2 * quoteAfterSwp2; + // Allow a small tolerance for rounding error. + assertApproxEqRel(invariantBefore, invariantAfterSwp1, 1e16); + assertApproxEqRel(invariantBefore, invariantAfterSwp2, 1e16); + } + + /* + * Test case for listenedOnce. If the user attempts to call getPrice too quickly, + * it should revert. + */ + function test_ListenedOnce() public { + vm.startPrank(SWAPPER1_ADDR); + amm.listen(); + vm.warp(block.timestamp + 11); + amm.getPrice(); + vm.expectRevert(); + amm.getPrice(); + vm.stopPrank(); + } +} From 1c146fa8fe22a6c29c4996c5748091c50e99b336 Mon Sep 17 00:00:00 2001 From: Hrishabh Ayush Date: Fri, 7 Feb 2025 23:26:58 -0500 Subject: [PATCH 2/9] Updated Riff.sol and Riff.t.sol with required functionality --- musical-charts/script/ViolinAMM.s.sol | 25 ------ .../src/{ViolinAMM.sol => Riff.sol} | 55 ++---------- musical-charts/src/ViolinCoin.sol | 6 +- .../test/{ViolinAMM.t.sol => Riff.t.sol} | 88 +++++++------------ 4 files changed, 43 insertions(+), 131 deletions(-) delete mode 100644 musical-charts/script/ViolinAMM.s.sol rename musical-charts/src/{ViolinAMM.sol => Riff.sol} (77%) rename musical-charts/test/{ViolinAMM.t.sol => Riff.t.sol} (78%) diff --git a/musical-charts/script/ViolinAMM.s.sol b/musical-charts/script/ViolinAMM.s.sol deleted file mode 100644 index 047dd95..0000000 --- a/musical-charts/script/ViolinAMM.s.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {ViolinAMM} from "../src/ViolinAMM.sol"; -import {ViolinCoin} from "../src/ViolinCoin.sol"; - -contract ViolinAMMScript is Script { - ViolinAMM public amm; - ViolinCoin public baseAsset; - ViolinCoin public quoteAsset; - - function setUp() public { - address admin = address(0x14dC79964da2C08b23698B3D3cc7Ca32193d9955); - baseAsset = new ViolinCoin(admin, "Base Asset", "BASE", 18); - quoteAsset = new ViolinCoin(admin, "Quote Asset", "QUOTE", 18); - } - - function run() public { - vm.startBroadcast(); - address admin = address(0x14dC79964da2C08b23698B3D3cc7Ca32193d9955); - amm = new ViolinAMM(baseAsset, quoteAsset, 1e18, 1e18, admin); - vm.stopBroadcast(); - } -} diff --git a/musical-charts/src/ViolinAMM.sol b/musical-charts/src/Riff.sol similarity index 77% rename from musical-charts/src/ViolinAMM.sol rename to musical-charts/src/Riff.sol index 6b354ef..5e98466 100644 --- a/musical-charts/src/ViolinAMM.sol +++ b/musical-charts/src/Riff.sol @@ -16,7 +16,7 @@ import "./ViolinCoin.sol"; // ViolinAMM Contract //////////////////////////////////////////////////////////////*/ -contract ViolinAMM is ReentrancyGuard { +contract Riff is ReentrancyGuard { /*////////////////////////////////////////////////////////////// // TOKEN STORAGE //////////////////////////////////////////////////////////////*/ @@ -28,9 +28,14 @@ contract ViolinAMM is ReentrancyGuard { //////////////////////////////////////////////////////////////*/ saddress adminAddress; + // Fixed point arithmetic unit suint256 wad; + + // Price reveal threshold suint256 priceReveal; + // Since the reserves are encrypted, people can't access + // the price information until they swap suint256 baseReserve; suint256 quoteReserve; @@ -41,10 +46,6 @@ contract ViolinAMM is ReentrancyGuard { // EVENTS //////////////////////////////////////////////////////////////*/ - // Listening event should be an encrypted event - /// @notice Emitted when a user puts a request to listen - event Listening(address indexed user); - /// @notice Emitted when a swap is executed by the user event SwapExecuted(address indexed user); @@ -66,7 +67,6 @@ contract ViolinAMM is ReentrancyGuard { function listen() external { hasListened[saddress(msg.sender)] = sbool(true); lastListenedTimestamp[saddress(msg.sender)] = suint256(block.timestamp); - emit Listening(msg.sender); } /*////////////////////////////////////////////////////////////// @@ -111,12 +111,8 @@ contract ViolinAMM is ReentrancyGuard { * direction. */ function swap(suint256 baseIn, suint256 quoteIn) public nonReentrant onlyViolinListener { - // After listening to the music, the user can call this function to swap the assets - // Price gets revealed here - require( - suint256(block.timestamp) >= suint256(lastListenedTimestamp[saddress(msg.sender)]) + suint256(10 seconds), - "Must wait 10 seconds before calling swap" - ); + // After listening to the music, the swapper can call this function to swap the assets, + // then the price gets revealed to the swapper suint256 baseOut; suint256 quoteOut; @@ -149,14 +145,7 @@ contract ViolinAMM is ReentrancyGuard { } /* - * Only returns price if it's above priceReveal threshold. - */ - function getPriceGated() external view requirePriceSufficient returns (uint256 price) { - return uint256(_computePrice()); - } - - /* - * Bypasses priceReveal threshold. For testing purposes. + * Returns price of quote asset. */ function getPrice() external onlyViolinListener returns (uint256 price) { hasListened[saddress(msg.sender)] = sbool(false); @@ -180,30 +169,4 @@ contract ViolinAMM is ReentrancyGuard { ); z = (x * y) / denominator; } - - /* - * Assert price of quote asset above priceReveal threshold. - */ - modifier requirePriceSufficient() { - require(_computePrice() >= priceReveal, "Price of quote asset is not high enough."); - _; - } - - /*////////////////////////////////////////////////////////////// - // GETTERS - //////////////////////////////////////////////////////////////*/ - - /* - * Get the base reserve - */ - function getBaseReserve() external view returns (uint256) { - return uint256(baseReserve); - } - - /* - * Get the quote reserve - */ - function getQuoteReserve() external view returns (uint256) { - return uint256(quoteReserve); - } } diff --git a/musical-charts/src/ViolinCoin.sol b/musical-charts/src/ViolinCoin.sol index 678e8e6..bbd098f 100644 --- a/musical-charts/src/ViolinCoin.sol +++ b/musical-charts/src/ViolinCoin.sol @@ -36,16 +36,16 @@ contract ViolinCoin is SRC20, IViolinCoin { /// @notice Returns the balance of msg.sender. function balanceOf() public view override(ISRC20, SRC20) returns (uint256) { - return SRC20.balanceOf(); + return super.balanceOf(); } /// @notice Transfers tokens to another address. function transfer(saddress to, suint256 amount) public override(ISRC20, SRC20) returns (bool) { - return SRC20.transfer(to, amount); + return super.transfer(to, amount); } /// @notice Transfers tokens from one address to another. function transferFrom(saddress from, saddress to, suint256 amount) public override(ISRC20, SRC20) returns (bool) { - return SRC20.transferFrom(from, to, amount); + return super.transferFrom(from, to, amount); } } diff --git a/musical-charts/test/ViolinAMM.t.sol b/musical-charts/test/Riff.t.sol similarity index 78% rename from musical-charts/test/ViolinAMM.t.sol rename to musical-charts/test/Riff.t.sol index 5565d21..b43ff5e 100644 --- a/musical-charts/test/ViolinAMM.t.sol +++ b/musical-charts/test/Riff.t.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.13; import "solmate/tokens/ERC20.sol"; import {MockERC20} from "solmate/test/utils/mocks/MockERC20.sol"; -import "../src/ViolinAMM.sol"; +import "../src/Riff.sol"; import {Test, console} from "forge-std/Test.sol"; /*////////////////////////////////////////////////////////////// @@ -14,7 +14,7 @@ contract ViolinAMMTest is Test { /*////////////////////////////////////////////////////////////// // AMM STORAGE //////////////////////////////////////////////////////////////*/ - ViolinAMM public amm; + Riff public amm; /*////////////////////////////////////////////////////////////// // TOKEN STORAGE @@ -43,7 +43,7 @@ contract ViolinAMMTest is Test { quoteAsset = new ViolinCoin(address(this), "Chainlink", "LINK", 18); // Start with pool price 1 LINK = 25 USDC - amm = new ViolinAMM(ViolinCoin(address(baseAsset)), ViolinCoin(address(quoteAsset)), WAD, 25 * WAD, testAdmin); + amm = new Riff(ViolinCoin(address(baseAsset)), ViolinCoin(address(quoteAsset)), WAD, 25 * WAD, testAdmin); baseAsset.mint(saddress(address(this)), suint256(200000 * WAD)); quoteAsset.mint(saddress(address(this)), suint256(10000 * WAD)); baseAsset.approve(saddress(address(amm)), suint256(200000 * WAD)); @@ -79,19 +79,15 @@ contract ViolinAMMTest is Test { function test_PriceUp() public { vm.startPrank(SWAPPER1_ADDR); amm.listen(); - vm.warp(block.timestamp + 11); - uint256 priceT0 = amm.getPrice(); uint256 swapperBaseT0 = baseAsset.balanceOf(); uint256 swapperQuoteT0 = quoteAsset.balanceOf(); baseAsset.approve(saddress(address(amm)), suint256(30000 * WAD)); amm.listen(); - vm.warp(block.timestamp + 11); amm.swap(suint256(30000 * WAD), suint256(0)); amm.listen(); - vm.warp(block.timestamp + 11); assertLt(priceT0, amm.getPrice()); assertGt(swapperBaseT0, baseAsset.balanceOf()); assertLt(swapperQuoteT0, quoteAsset.balanceOf()); @@ -104,67 +100,42 @@ contract ViolinAMMTest is Test { */ function test_PriceNetDown() public { vm.startPrank(SWAPPER1_ADDR); - vm.warp(block.timestamp + 11); + amm.listen(); uint256 priceT0 = amm.getPrice(); baseAsset.approve(saddress(address(amm)), suint256(5000 * WAD)); amm.listen(); - vm.warp(block.timestamp + 11); amm.swap(suint256(5000 * WAD), suint256(0)); vm.stopPrank(); vm.startPrank(SWAPPER2_ADDR); - vm.warp(block.timestamp + 11); + amm.listen(); quoteAsset.approve(saddress(address(amm)), suint256(5000 * WAD)); + amm.listen(); amm.swap(suint256(0), suint256(5000 * WAD)); amm.listen(); - vm.warp(block.timestamp + 11); assertGt(priceT0, amm.getPrice()); vm.stopPrank(); } - /* - * Test case for revealing price. If the price is not above the threshold, - * getPriceGated should revert. - */ - function test_PriceReveal() public { - // Shouldn't see price when 1 LINK = 20 USDC - vm.expectRevert(); - amm.getPriceGated(); - - // Should see price when 1 LINK = 31 USDC after this swap - vm.startPrank(SWAPPER1_ADDR); - vm.warp(block.timestamp + 11); - baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); - amm.swap(suint256(50000 * WAD), suint256(0)); - vm.stopPrank(); - amm.getPriceGated(); - } - /* * Test case for swap timing. If the user attempts to swap too quickly, * the swap should revert. */ function test_SwapTiming() public { vm.startPrank(SWAPPER1_ADDR); - + amm.listen(); baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); - - // Immediately attempt another swap - // Should revert due to timing restriction - vm.expectRevert("Must wait 10 seconds before calling swap"); - amm.swap(suint256(50000 * WAD), suint256(0)); - - // Wait 10 seconds and try again - vm.warp(block.timestamp + 11); - amm.swap(suint256(50000 * WAD), suint256(0)); + amm.swap(suint256(5000 * WAD), suint256(0)); + vm.expectRevert(); + amm.swap(suint256(5000 * WAD), suint256(0)); vm.stopPrank(); } /* * Test case for access control. If the user is not a listener, they should - * not be able to call swap or getPriceGated. + * not be able to call swap or getPrice. */ function test_Access() public { // Non-listener should not be able to call swap @@ -176,16 +147,16 @@ contract ViolinAMMTest is Test { // Unauthorized call to getPriceGated should revert vm.startPrank(NON_LISTENER_ADDR); vm.expectRevert(); - amm.getPriceGated(); + amm.getPrice(); vm.stopPrank(); // After the address gains listener status, they can call swap vm.startPrank(NON_LISTENER_ADDR); - baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); amm.listen(); - vm.warp(block.timestamp + 11); + baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); amm.swap(suint256(50000 * WAD), suint256(0)); - amm.getPriceGated(); + amm.listen(); + amm.getPrice(); vm.stopPrank(); } @@ -196,22 +167,18 @@ contract ViolinAMMTest is Test { function test_ZeroSwap() public { vm.startPrank(SWAPPER1_ADDR); amm.listen(); - vm.warp(block.timestamp + 11); uint256 priceT0 = amm.getPrice(); baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); amm.listen(); - vm.warp(block.timestamp + 11); amm.swap(suint256(0), suint256(0)); vm.stopPrank(); vm.startPrank(SWAPPER2_ADDR); amm.listen(); - vm.warp(block.timestamp + 11); quoteAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); amm.swap(suint256(0), suint256(0)); amm.listen(); - vm.warp(block.timestamp + 11); assertEq(priceT0, amm.getPrice()); vm.stopPrank(); } @@ -222,35 +189,43 @@ contract ViolinAMMTest is Test { * error. */ function test_LiquidityInvariance() public { - uint256 baseBefore = amm.getBaseReserve(); - uint256 quoteBefore = amm.getQuoteReserve(); + vm.startPrank(address(this)); + amm.listen(); + uint256 baseBefore = baseAsset.balanceOf(); + uint256 quoteBefore = quoteAsset.balanceOf(); uint256 invariantBefore = baseBefore * quoteBefore; + vm.stopPrank(); // Have two different listeners perform swaps vm.startPrank(SWAPPER1_ADDR); - vm.warp(block.timestamp + 11); + amm.listen(); baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); amm.swap(suint256(500 * WAD), suint256(0)); vm.stopPrank(); - uint256 baseAfterSwp1 = amm.getBaseReserve(); - uint256 quoteAfterSwp1 = amm.getQuoteReserve(); + uint256 baseAfterSwp1 = baseAsset.balanceOf(); + uint256 quoteAfterSwp1 = quoteAsset.balanceOf(); uint256 invariantAfterSwp1 = baseAfterSwp1 * quoteAfterSwp1; vm.startPrank(SWAPPER2_ADDR); - vm.warp(block.timestamp + 11); + amm.listen(); baseAsset.approve(saddress(address(amm)), suint256(20000 * WAD)); amm.swap(suint256(200 * WAD), suint256(0)); vm.stopPrank(); - uint256 baseAfterSwp2 = amm.getBaseReserve(); - uint256 quoteAfterSwp2 = amm.getQuoteReserve(); + vm.startPrank(address(this)); + amm.listen(); + uint256 baseAfterSwp2 = baseAsset.balanceOf(); + uint256 quoteAfterSwp2 = quoteAsset.balanceOf(); uint256 invariantAfterSwp2 = baseAfterSwp2 * quoteAfterSwp2; + vm.stopPrank(); + // Allow a small tolerance for rounding error. assertApproxEqRel(invariantBefore, invariantAfterSwp1, 1e16); assertApproxEqRel(invariantBefore, invariantAfterSwp2, 1e16); + vm.stopPrank(); } /* @@ -260,7 +235,6 @@ contract ViolinAMMTest is Test { function test_ListenedOnce() public { vm.startPrank(SWAPPER1_ADDR); amm.listen(); - vm.warp(block.timestamp + 11); amm.getPrice(); vm.expectRevert(); amm.getPrice(); From fce38ca78b0fc740b4301c6d00eb2c0f640a7bd9 Mon Sep 17 00:00:00 2001 From: Hrishabh Ayush Date: Sat, 8 Feb 2025 11:05:54 -0500 Subject: [PATCH 3/9] README updated --- musical-charts/README.md | 61 ++++------------------------------------ 1 file changed, 5 insertions(+), 56 deletions(-) diff --git a/musical-charts/README.md b/musical-charts/README.md index 9b2cfac..1377cc3 100644 --- a/musical-charts/README.md +++ b/musical-charts/README.md @@ -1,64 +1,13 @@ -# Musical Charts +# RIFF ## Overview -**Musical Charts** work on creating an Automated Market Maker (AMM) where people can swap tokens only after listening to music. Users experience market trends through AI generated violin music, and they are allowed to make a trade signal after they have listened to the generated music. The project is more fun because each trade triggers a new violin melody based on the market data, ensuring a fresh and engaging experience every time for the user. +**Riff** work on creating an Automated Market Maker (AMM) where people can swap tokens only after listening to music. Users experience market trends through AI generated violin music, and they are allowed to make a trade signal after they have listened to the generated music. The project is more fun because each trade triggers a new violin melody based on the market data, ensuring a fresh and engaging experience every time for the user. ## Problem -Price data of any token or memecoin alone is not engaging to a general user and often user doesn't feel the thrill or enjoyment while trading these coins. +Trading experience on every AMM is the same, it's time for something radically different like Riff. ## Insight -Restricting financial signals of buy/sell through music can foster a more intuitive and emotional connection to market trends. +We can use other senses like music sound to make trading calls. ## Solution -Encrypt and process real-time price data in a *Trusted Execution Environment* (TEE) to generate violin compositions, allowing users to ‘listen’ to market changes before they swap. - -## Goals -Focus on engagement and excitement rather than commercial viability, providing a unique, shareable experience. - -## Get Involved -Join us in redefining data engagement and creating a fun trading experience through sound! - -## Usage - -### Build - -```shell -$ sforge build -``` - -### Test - -```shell -$ sforge test -``` - -### Format - -```shell -$ sforge fmt -``` - -### Gas Snapshots - -```shell -$ sforge snapshot -``` - -### Anvil - -```shell -$ sanvil -``` - -### Deploy - -```shell -$ sforge script script/ViolinAMM.s.sol:ViolinAMMScript --rpc-url --private-key -``` - -### Help - -```shell -$ sforge --help -$ sanvil --help -``` +Force people to listen to price charts, where the encrypted price is only accessible by an AI agent that plays music to anyone that wants to get price information \ No newline at end of file From a176f72909b761e69504ec96a67e8e4b13eac588 Mon Sep 17 00:00:00 2001 From: Hrishabh Ayush Date: Sun, 9 Feb 2025 14:32:55 -0500 Subject: [PATCH 4/9] Contract and test cases updated without any listening logic for the user --- musical-charts/src/Riff.sol | 41 +++------- musical-charts/test/Riff.t.sol | 141 ++++++++++++++------------------- 2 files changed, 67 insertions(+), 115 deletions(-) diff --git a/musical-charts/src/Riff.sol b/musical-charts/src/Riff.sol index 5e98466..c688139 100644 --- a/musical-charts/src/Riff.sol +++ b/musical-charts/src/Riff.sol @@ -28,47 +28,28 @@ contract Riff is ReentrancyGuard { //////////////////////////////////////////////////////////////*/ saddress adminAddress; + saddress violinAddress; + // Fixed point arithmetic unit suint256 wad; - // Price reveal threshold - suint256 priceReveal; - // Since the reserves are encrypted, people can't access // the price information until they swap suint256 baseReserve; suint256 quoteReserve; - mapping(saddress => sbool) hasListened; - mapping(saddress => suint256) lastListenedTimestamp; - - /*////////////////////////////////////////////////////////////// - // EVENTS - //////////////////////////////////////////////////////////////*/ - - /// @notice Emitted when a swap is executed by the user - event SwapExecuted(address indexed user); - /*////////////////////////////////////////////////////////////// // MODIFIERS //////////////////////////////////////////////////////////////*/ /* - * Only listener can call this function + * Only off-chain violin can call this function */ modifier onlyViolinListener() { - require(hasListened[saddress(msg.sender)], "You are not the listener"); + require(saddress(msg.sender) == violinAddress, "You don't have violin access"); _; } - /* - * Listen to the music - */ - function listen() external { - hasListened[saddress(msg.sender)] = sbool(true); - lastListenedTimestamp[saddress(msg.sender)] = suint256(block.timestamp); - } - /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ @@ -76,18 +57,18 @@ contract Riff is ReentrancyGuard { ViolinCoin _baseAsset, ViolinCoin _quoteAsset, uint256 _wad, - uint256 _priceReveal, - address _adminAddress + address _adminAddress, + address _violinAddress ) { baseAsset = _baseAsset; quoteAsset = _quoteAsset; adminAddress = saddress(_adminAddress); + violinAddress = saddress(_violinAddress); // Stored as suint256 for convenience. Not actually shielded bc it's a // transparent parameter in the constructor wad = suint256(_wad); - priceReveal = suint256(_priceReveal); } /*////////////////////////////////////////////////////////////// AMM LOGIC @@ -110,7 +91,7 @@ contract Riff is ReentrancyGuard { * Wrapper around swap so calldata for trade looks the same regardless of * direction. */ - function swap(suint256 baseIn, suint256 quoteIn) public nonReentrant onlyViolinListener { + function swap(suint256 baseIn, suint256 quoteIn) public nonReentrant { // After listening to the music, the swapper can call this function to swap the assets, // then the price gets revealed to the swapper @@ -119,9 +100,6 @@ contract Riff is ReentrancyGuard { (baseOut, baseReserve, quoteReserve) = _swap(baseAsset, quoteAsset, baseReserve, quoteReserve, baseIn); (quoteOut, quoteReserve, baseReserve) = _swap(quoteAsset, baseAsset, quoteReserve, baseReserve, quoteIn); - - emit SwapExecuted(msg.sender); - hasListened[saddress(msg.sender)] = sbool(false); } /* @@ -147,8 +125,7 @@ contract Riff is ReentrancyGuard { /* * Returns price of quote asset. */ - function getPrice() external onlyViolinListener returns (uint256 price) { - hasListened[saddress(msg.sender)] = sbool(false); + function getPrice() external view onlyViolinListener returns (uint256 price) { return uint256(_computePrice()); } diff --git a/musical-charts/test/Riff.t.sol b/musical-charts/test/Riff.t.sol index b43ff5e..a4e8fed 100644 --- a/musical-charts/test/Riff.t.sol +++ b/musical-charts/test/Riff.t.sol @@ -27,6 +27,8 @@ contract ViolinAMMTest is Test { //////////////////////////////////////////////////////////////*/ address testAdmin = address(0xabcd); + address constant violinAddress = address(0x123); + uint256 constant WAD = 1e18; uint8 constant WAD_ZEROS = 18; @@ -42,8 +44,8 @@ contract ViolinAMMTest is Test { baseAsset = new ViolinCoin(address(this), "Circle", "USDC", 18); quoteAsset = new ViolinCoin(address(this), "Chainlink", "LINK", 18); - // Start with pool price 1 LINK = 25 USDC - amm = new Riff(ViolinCoin(address(baseAsset)), ViolinCoin(address(quoteAsset)), WAD, 25 * WAD, testAdmin); + // Start with pool price 1 LINK = 20 USDC + amm = new Riff(ViolinCoin(address(baseAsset)), ViolinCoin(address(quoteAsset)), WAD, testAdmin, violinAddress); baseAsset.mint(saddress(address(this)), suint256(200000 * WAD)); quoteAsset.mint(saddress(address(this)), suint256(10000 * WAD)); baseAsset.approve(saddress(address(amm)), suint256(200000 * WAD)); @@ -59,127 +61,117 @@ contract ViolinAMMTest is Test { // Another address that starts with 50k units of each, LINK and USDC baseAsset.mint(saddress(NON_LISTENER_ADDR), suint256(50000 * WAD)); quoteAsset.mint(saddress(NON_LISTENER_ADDR), suint256(50000 * WAD)); + } + + /*////////////////////////////////////////////////////////////// + // TEST CASES + //////////////////////////////////////////////////////////////*/ + + /* + * Test case for zero swap. If the user attempts to swap zero of both assets, + * then there is no change in the price. + */ + function test_ZeroSwap() public { + // Fetch the initial price as violin + vm.startPrank(violinAddress); + uint256 priceT0 = amm.getPrice(); + vm.stopPrank(); - // Request violin access for test accounts + // Now try a zero swap of base vm.startPrank(SWAPPER1_ADDR); - amm.listen(); + baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); + amm.swap(suint256(0), suint256(0)); vm.stopPrank(); + // Another user attempts a zero swap of quote vm.startPrank(SWAPPER2_ADDR); - amm.listen(); + quoteAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); + amm.swap(suint256(0), suint256(0)); + vm.stopPrank(); + + // Finally access the price as the violin + vm.startPrank(violinAddress); + assertEq(priceT0, amm.getPrice()); vm.stopPrank(); } - /*////////////////////////////////////////////////////////////// - // TEST CASES - //////////////////////////////////////////////////////////////*/ /* * Test case for price going up after swap */ function test_PriceUp() public { - vm.startPrank(SWAPPER1_ADDR); - amm.listen(); + vm.startPrank(violinAddress); uint256 priceT0 = amm.getPrice(); + vm.stopPrank(); + + vm.startPrank(SWAPPER1_ADDR); uint256 swapperBaseT0 = baseAsset.balanceOf(); uint256 swapperQuoteT0 = quoteAsset.balanceOf(); baseAsset.approve(saddress(address(amm)), suint256(30000 * WAD)); - amm.listen(); amm.swap(suint256(30000 * WAD), suint256(0)); + vm.stopPrank(); - amm.listen(); + vm.startPrank(violinAddress); assertLt(priceT0, amm.getPrice()); + vm.stopPrank(); + + console.log("swapperBaseT0", swapperBaseT0); + console.log("baseAsset.balanceOf()", baseAsset.balanceOf()); + console.log("swapperQuoteT0", swapperQuoteT0); + console.log("quoteAsset.balanceOf()", quoteAsset.balanceOf()); assertGt(swapperBaseT0, baseAsset.balanceOf()); assertLt(swapperQuoteT0, quoteAsset.balanceOf()); - - vm.stopPrank(); } /* * Test case for price going down after swap. */ function test_PriceNetDown() public { - vm.startPrank(SWAPPER1_ADDR); - amm.listen(); + vm.startPrank(violinAddress); uint256 priceT0 = amm.getPrice(); + vm.stopPrank(); + + vm.startPrank(SWAPPER1_ADDR); baseAsset.approve(saddress(address(amm)), suint256(5000 * WAD)); - amm.listen(); amm.swap(suint256(5000 * WAD), suint256(0)); vm.stopPrank(); vm.startPrank(SWAPPER2_ADDR); - amm.listen(); quoteAsset.approve(saddress(address(amm)), suint256(5000 * WAD)); - amm.listen(); amm.swap(suint256(0), suint256(5000 * WAD)); + vm.stopPrank(); - amm.listen(); + vm.startPrank(violinAddress); assertGt(priceT0, amm.getPrice()); - vm.stopPrank(); } /* - * Test case for swap timing. If the user attempts to swap too quickly, - * the swap should revert. + * Test case for access control. Only the violin can call getPrice. */ - function test_SwapTiming() public { + function test_AccessControl() public { vm.startPrank(SWAPPER1_ADDR); - amm.listen(); - baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); - amm.swap(suint256(5000 * WAD), suint256(0)); - vm.expectRevert(); - amm.swap(suint256(5000 * WAD), suint256(0)); - vm.stopPrank(); - } - - /* - * Test case for access control. If the user is not a listener, they should - * not be able to call swap or getPrice. - */ - function test_Access() public { - // Non-listener should not be able to call swap - vm.startPrank(NON_LISTENER_ADDR); - vm.expectRevert("You are not the listener"); - amm.swap(suint256(50000 * WAD), suint256(0)); - vm.stopPrank(); - - // Unauthorized call to getPriceGated should revert - vm.startPrank(NON_LISTENER_ADDR); - vm.expectRevert(); + vm.expectRevert("You don't have violin access"); amm.getPrice(); vm.stopPrank(); - // After the address gains listener status, they can call swap - vm.startPrank(NON_LISTENER_ADDR); - amm.listen(); - baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); - amm.swap(suint256(50000 * WAD), suint256(0)); - amm.listen(); + vm.startPrank(violinAddress); amm.getPrice(); vm.stopPrank(); } /* - * Test case for zero swap. If the user attempts to swap zero of both assets, - * then there is no change in the price. + * Test case for swap access control. Any user can call swap */ - function test_ZeroSwap() public { + function test_SwapAccessControl() public { vm.startPrank(SWAPPER1_ADDR); - amm.listen(); - uint256 priceT0 = amm.getPrice(); - baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); - amm.listen(); - amm.swap(suint256(0), suint256(0)); + baseAsset.approve(saddress(address(amm)), suint256(5000 * WAD)); + amm.swap(suint256(5000 * WAD), suint256(0)); vm.stopPrank(); vm.startPrank(SWAPPER2_ADDR); - amm.listen(); - quoteAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); - amm.swap(suint256(0), suint256(0)); - - amm.listen(); - assertEq(priceT0, amm.getPrice()); + quoteAsset.approve(saddress(address(amm)), suint256(5000 * WAD)); + amm.swap(suint256(0), suint256(5000 * WAD)); vm.stopPrank(); } @@ -190,7 +182,6 @@ contract ViolinAMMTest is Test { */ function test_LiquidityInvariance() public { vm.startPrank(address(this)); - amm.listen(); uint256 baseBefore = baseAsset.balanceOf(); uint256 quoteBefore = quoteAsset.balanceOf(); @@ -199,7 +190,6 @@ contract ViolinAMMTest is Test { // Have two different listeners perform swaps vm.startPrank(SWAPPER1_ADDR); - amm.listen(); baseAsset.approve(saddress(address(amm)), suint256(50000 * WAD)); amm.swap(suint256(500 * WAD), suint256(0)); vm.stopPrank(); @@ -210,13 +200,11 @@ contract ViolinAMMTest is Test { uint256 invariantAfterSwp1 = baseAfterSwp1 * quoteAfterSwp1; vm.startPrank(SWAPPER2_ADDR); - amm.listen(); baseAsset.approve(saddress(address(amm)), suint256(20000 * WAD)); amm.swap(suint256(200 * WAD), suint256(0)); vm.stopPrank(); vm.startPrank(address(this)); - amm.listen(); uint256 baseAfterSwp2 = baseAsset.balanceOf(); uint256 quoteAfterSwp2 = quoteAsset.balanceOf(); uint256 invariantAfterSwp2 = baseAfterSwp2 * quoteAfterSwp2; @@ -227,17 +215,4 @@ contract ViolinAMMTest is Test { assertApproxEqRel(invariantBefore, invariantAfterSwp2, 1e16); vm.stopPrank(); } - - /* - * Test case for listenedOnce. If the user attempts to call getPrice too quickly, - * it should revert. - */ - function test_ListenedOnce() public { - vm.startPrank(SWAPPER1_ADDR); - amm.listen(); - amm.getPrice(); - vm.expectRevert(); - amm.getPrice(); - vm.stopPrank(); - } } From 1894ae0bef648fdf428cbcd23e5edbdc229d2d62 Mon Sep 17 00:00:00 2001 From: Hrishabh Ayush Date: Sun, 9 Feb 2025 14:52:42 -0500 Subject: [PATCH 5/9] README update --- musical-charts/README.md | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/musical-charts/README.md b/musical-charts/README.md index 1377cc3..b386b15 100644 --- a/musical-charts/README.md +++ b/musical-charts/README.md @@ -1,13 +1,10 @@ # RIFF -## Overview -**Riff** work on creating an Automated Market Maker (AMM) where people can swap tokens only after listening to music. Users experience market trends through AI generated violin music, and they are allowed to make a trade signal after they have listened to the generated music. The project is more fun because each trade triggers a new violin melody based on the market data, ensuring a fresh and engaging experience every time for the user. - ## Problem -Trading experience on every AMM is the same, it's time for something radically different like Riff. +Trading experience on every Automated Market Maker (AMM) is the same, it's time for something radically different like Riff. ## Insight -We can use other senses like music sound to make trading calls. +What if we used other senses like hearing to make calls? ## Solution -Force people to listen to price charts, where the encrypted price is only accessible by an AI agent that plays music to anyone that wants to get price information \ No newline at end of file +Make a price chart that you can listen to - users pay to listen to reveal interesting market trends. \ No newline at end of file From b905b8bf393b45fcd15fe4b42aeb2e25e8611352 Mon Sep 17 00:00:00 2001 From: Hrishabh Ayush Date: Sun, 9 Feb 2025 15:17:01 -0500 Subject: [PATCH 6/9] Resolved failing PriceUp test case --- musical-charts/test/Riff.t.sol | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/musical-charts/test/Riff.t.sol b/musical-charts/test/Riff.t.sol index a4e8fed..af96a86 100644 --- a/musical-charts/test/Riff.t.sol +++ b/musical-charts/test/Riff.t.sol @@ -109,18 +109,17 @@ contract ViolinAMMTest is Test { baseAsset.approve(saddress(address(amm)), suint256(30000 * WAD)); amm.swap(suint256(30000 * WAD), suint256(0)); + + uint256 swapperBaseT1 = baseAsset.balanceOf(); + uint256 swapperQuoteT1 = quoteAsset.balanceOf(); vm.stopPrank(); vm.startPrank(violinAddress); assertLt(priceT0, amm.getPrice()); vm.stopPrank(); - console.log("swapperBaseT0", swapperBaseT0); - console.log("baseAsset.balanceOf()", baseAsset.balanceOf()); - console.log("swapperQuoteT0", swapperQuoteT0); - console.log("quoteAsset.balanceOf()", quoteAsset.balanceOf()); - assertGt(swapperBaseT0, baseAsset.balanceOf()); - assertLt(swapperQuoteT0, quoteAsset.balanceOf()); + assertGt(swapperBaseT0, swapperBaseT1); + assertLt(swapperQuoteT0, swapperQuoteT1); } /* From dec9bd260a5945bb83175766bddaeb6535e85156 Mon Sep 17 00:00:00 2001 From: Hrishabh Ayush Date: Mon, 10 Feb 2025 20:35:33 -0500 Subject: [PATCH 7/9] Updated root README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5141240..5228730 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,8 @@ Each project in this repo lives in its own directory and includes a dedicated RE Below is a quick summary of each prototype currently available in this repository: -1. **`Project 1 here`** - Description here. +1. **`Riff`** + A bonding curve that you can hear. 2. **`Project 2 here`** Description here. From d2267d965333f9427c0b1fad1101f1c579ac3b8c Mon Sep 17 00:00:00 2001 From: Hrishabh Ayush Date: Tue, 11 Feb 2025 10:23:38 -0500 Subject: [PATCH 8/9] README solution update --- musical-charts/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/musical-charts/README.md b/musical-charts/README.md index b386b15..6ac1e15 100644 --- a/musical-charts/README.md +++ b/musical-charts/README.md @@ -7,4 +7,4 @@ Trading experience on every Automated Market Maker (AMM) is the same, it's time What if we used other senses like hearing to make calls? ## Solution -Make a price chart that you can listen to - users pay to listen to reveal interesting market trends. \ No newline at end of file +Encrypt a bonding curve to create an asset with a price that no one can see. Let an AI violin be the only party that can see the price. The violin generates music whenever it sees price fluctuations. Now, instead of seeing a price chart, users need to listen to it. \ No newline at end of file From 74f956967e4eb6fa68dc773d9619f7afe3c07e41 Mon Sep 17 00:00:00 2001 From: Hrishabh Ayush Date: Tue, 11 Feb 2025 12:31:39 -0500 Subject: [PATCH 9/9] folder musical-charts changed to riff --- .gitmodules | 8 ++++---- {musical-charts => riff}/.gitignore | 0 {musical-charts => riff}/README.md | 0 {musical-charts => riff}/foundry.toml | 0 {musical-charts => riff}/lib/forge-std | 0 {musical-charts => riff}/lib/solmate | 0 {musical-charts => riff}/src/Riff.sol | 0 {musical-charts => riff}/src/SRC20.sol | 0 {musical-charts => riff}/src/ViolinCoin.sol | 0 {musical-charts => riff}/test/Riff.t.sol | 0 10 files changed, 4 insertions(+), 4 deletions(-) rename {musical-charts => riff}/.gitignore (100%) rename {musical-charts => riff}/README.md (100%) rename {musical-charts => riff}/foundry.toml (100%) rename {musical-charts => riff}/lib/forge-std (100%) rename {musical-charts => riff}/lib/solmate (100%) rename {musical-charts => riff}/src/Riff.sol (100%) rename {musical-charts => riff}/src/SRC20.sol (100%) rename {musical-charts => riff}/src/ViolinCoin.sol (100%) rename {musical-charts => riff}/test/Riff.t.sol (100%) diff --git a/.gitmodules b/.gitmodules index 5270f1a..5183b0c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ -[submodule "musical-charts/lib/forge-std"] - path = musical-charts/lib/forge-std +[submodule "riff/lib/forge-std"] + path = riff/lib/forge-std url = https://github.com/foundry-rs/forge-std -[submodule "musical-charts/lib/solmate"] - path = musical-charts/lib/solmate +[submodule "riff/lib/solmate"] + path = riff/lib/solmate url = https://github.com/Rari-Capital/solmate diff --git a/musical-charts/.gitignore b/riff/.gitignore similarity index 100% rename from musical-charts/.gitignore rename to riff/.gitignore diff --git a/musical-charts/README.md b/riff/README.md similarity index 100% rename from musical-charts/README.md rename to riff/README.md diff --git a/musical-charts/foundry.toml b/riff/foundry.toml similarity index 100% rename from musical-charts/foundry.toml rename to riff/foundry.toml diff --git a/musical-charts/lib/forge-std b/riff/lib/forge-std similarity index 100% rename from musical-charts/lib/forge-std rename to riff/lib/forge-std diff --git a/musical-charts/lib/solmate b/riff/lib/solmate similarity index 100% rename from musical-charts/lib/solmate rename to riff/lib/solmate diff --git a/musical-charts/src/Riff.sol b/riff/src/Riff.sol similarity index 100% rename from musical-charts/src/Riff.sol rename to riff/src/Riff.sol diff --git a/musical-charts/src/SRC20.sol b/riff/src/SRC20.sol similarity index 100% rename from musical-charts/src/SRC20.sol rename to riff/src/SRC20.sol diff --git a/musical-charts/src/ViolinCoin.sol b/riff/src/ViolinCoin.sol similarity index 100% rename from musical-charts/src/ViolinCoin.sol rename to riff/src/ViolinCoin.sol diff --git a/musical-charts/test/Riff.t.sol b/riff/test/Riff.t.sol similarity index 100% rename from musical-charts/test/Riff.t.sol rename to riff/test/Riff.t.sol