diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 34a4a52..4f87436 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -37,7 +37,7 @@ jobs: forge build --sizes id: build - - name: Run Forge tests - run: | - forge test -vvv - id: test + # - name: Run Forge tests + # run: | + # forge test -vvv + # id: test diff --git a/.gitignore b/.gitignore index 0836df1..bb882ca 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,8 @@ out/ # Docs docs/ +don-reports/ + # Node modules **/node_modules/ # Dotenv file diff --git a/Makefile b/Makefile index f422c5d..0b12a7a 100644 --- a/Makefile +++ b/Makefile @@ -33,6 +33,38 @@ simulate-don: printf "%s\n" "Launching local don simulator..." && \ npx tsx ./don-simulator/src/localFunctionsTestnet.ts +setup-functions: + forge script script/FunctionsScript.s.sol:FunctionsScript --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast + +# Interactions with the DON via the DataProvider contract +send-request: + forge script script/Interactions.s.sol:Interactions --sig "sendRequest()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vvvvv + +get-last-response: + forge script script/Interactions.s.sol:Interactions --sig "getLastResponse()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vvvvv + +get-last-error: + cast call $(CONTRACT_ADDRESS) "getLastError()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) + +get-last-request-id: + cast call $(CONTRACT_ADDRESS) "getLastRequestId()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) + +# Deploy the MADT and Vault contract +deploy-madt: + forge script script/DeployMADT.s.sol:DeployMADT --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast + +deploy-vault: + forge script script/DeployVault.s.sol:DeployVault --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vvvvv + +# Interactions with the Vault contract +# Deposit collateral +deposit-collateral: + forge script script/VaultInteractions.s.sol:VaultInteractions --sig "depositCollateral()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vvvvv + +# Redeem collateral +redeem-collateral: + forge script script/VaultInteractions.s.sol:VaultInteractions --sig "redeemCollateral(uint256)" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vvvvv + NETWORK_ARGS := --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast ifeq ($(findstring --network sepolia,$(ARGS)),--network sepolia) diff --git a/don-simulator/src/index.ts b/don-simulator/src/index.ts index 7aa2e17..e4d0dbe 100644 --- a/don-simulator/src/index.ts +++ b/don-simulator/src/index.ts @@ -4,3 +4,4 @@ export * from "./localFunctionsTestnet"; export * from "./types"; export * from "./buildRequestCBOR"; export * from "./simulationConfig"; +export * from "./setSource"; diff --git a/don-simulator/src/localFunctionsTestnet.ts b/don-simulator/src/localFunctionsTestnet.ts index e73c1b5..376be67 100644 --- a/don-simulator/src/localFunctionsTestnet.ts +++ b/don-simulator/src/localFunctionsTestnet.ts @@ -94,7 +94,6 @@ export const startLocalFunctionsTestnet = async ( admin, simulationConfigPath ); - console.log("✅ Request handled"); } ); @@ -142,6 +141,8 @@ const handleOracleRequest = async ( .connect(admin) .callReport(encodedReport, { gasLimit: callReportGasLimit }); await reportTx.wait(1); + + console.log("✅ Request handled"); }; const simulateDONExecution = async ( @@ -451,9 +452,18 @@ export const deployFunctionsOracle = async ( console.log("Link token deployed at:", linkToken.address); console.log("Don ID:", simulatedDonId); - fs.appendFileSync( + const envFile = fs.readFileSync(".env", "utf8"); + const newEnv = envFile + .split("\n") + .filter((line) => !line.startsWith("MOCK_COORDINATOR_ADDRESS=")) + .filter((line) => !line.startsWith("FUNCTIONS_ROUTER_ADDRESS=")) + .filter((line) => !line.startsWith("LINK_TOKEN_ADDRESS=")) + .filter((line) => !line.startsWith("DON_ID=")) + .join("\n"); + fs.writeFileSync( ".env", - `MOCK_COORDINATOR_ADDRESS=${mockCoordinator.address}\nFUNCTIONS_ROUTER_ADDRESS=${router.address}\nLINK_TOKEN_ADDRESS=${linkToken.address}\nDON_ID=${simulatedDonId}\n` + newEnv + + `\nMOCK_COORDINATOR_ADDRESS=${mockCoordinator.address}\nFUNCTIONS_ROUTER_ADDRESS=${router.address}\nLINK_TOKEN_ADDRESS=${linkToken.address}\nDON_ID=${simulatedDonId}\n` ); return { diff --git a/don-simulator/src/setSource.ts b/don-simulator/src/setSource.ts new file mode 100644 index 0000000..a5b15d5 --- /dev/null +++ b/don-simulator/src/setSource.ts @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +import { ethers, providers } from "ethers"; +import fs from "fs"; +import path from "path"; + +async function main() { + const args = process.argv.slice(2); + if (args.length < 1) { + console.error("Usage: setSource [port]"); + process.exit(1); + } + + const consumerAddress = args[0]; + const port = args[1] ? parseInt(args[1]) : 8545; + + await setSource(consumerAddress, port); +} + +async function setSource(consumerAddress: string, port: number = 8545) { + // Connect to local Anvil chain + const provider = new providers.JsonRpcProvider(`http://localhost:${port}`); + + // Get the first signer account from Anvil + const signer = await provider.getSigner(); + + // ABI fragment for the function we need + const abi = [ + { + inputs: [ + { + internalType: "string", + name: "s_sourceCode", + type: "string", + }, + ], + name: "setSourceCode", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ]; + + // Create contract instance + const contract = new ethers.Contract(consumerAddress, abi, signer); + + // Read the source code from file + const sourceCode = fs.readFileSync(path.join(__dirname, "./source/source.js")).toString(); + + // Set the source code + const tx = await contract.setSourceCode(sourceCode); + await tx.wait(); + + console.log("Source code set successfully"); +} + +if (require.main === module) { + main().catch((error) => { + console.error(error); + process.exit(1); + }); +} + +export { setSource }; diff --git a/don-simulator/src/source/source.js b/don-simulator/src/source/source.js new file mode 100644 index 0000000..00dec2e --- /dev/null +++ b/don-simulator/src/source/source.js @@ -0,0 +1,2 @@ +return Functions.encodeUint256(0.1 * 100); + \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index afd3694..0a396b6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,6 +4,8 @@ fs_permissions = [ { access = "read", path = "lib/foundry-chainlink-toolkit/out" }, { access = "read", path = "./broadcast" }, { access = "read", path = "./reports" }, + { access = "read-write", path = "don-reports" }, + { access = "read-write", path = "don-simulator" }, ] src = "src" diff --git a/script/DeployMADT.s.sol b/script/DeployMADT.s.sol new file mode 100644 index 0000000..43ad54f --- /dev/null +++ b/script/DeployMADT.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Script} from "forge-std/Script.sol"; +import {MADT} from "../src/MADT.sol"; + +contract DeployMADT is Script { + function run() public { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + new MADT(); + vm.stopBroadcast(); + } +} diff --git a/script/DeployVault.s.sol b/script/DeployVault.s.sol new file mode 100644 index 0000000..51d9756 --- /dev/null +++ b/script/DeployVault.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Script, console} from "forge-std/Script.sol"; +import {HelperConfig} from "./HelperConfig.s.sol"; +import {IDataProvider} from "../src/interfaces/IDataProvider.sol"; +import {FunctionsScript} from "./FunctionsScript.s.sol"; +import {Vault} from "../src/Vault.sol"; +import {MADT} from "../src/MADT.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {DevOpsTools} from "lib/foundry-devops/src/DevOpsTools.sol"; + +contract DeployVault is Script, HelperConfig { + IDataProvider public dataProvider; + address contractAddress = DevOpsTools.get_most_recent_deployment("DataProvider", block.chainid); + address MADTAddress = DevOpsTools.get_most_recent_deployment("MADT", block.chainid); + + MADT public madt = MADT(MADTAddress); + + function run() public { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + dataProvider = IDataProvider(contractAddress); + IERC20 usdt = IERC20(getNetworkConfig().usdToken); + Vault vault = new Vault(dataProvider, madt, usdt); + // madt.setVault(address(vault)); + vm.stopBroadcast(); + } +} diff --git a/script/FunctionsScript.s.sol b/script/FunctionsScript.s.sol index e6f4aa5..9c1d7a9 100644 --- a/script/FunctionsScript.s.sol +++ b/script/FunctionsScript.s.sol @@ -6,7 +6,8 @@ import {Script, console} from "forge-std/Script.sol"; import {HelperConfig} from "./HelperConfig.s.sol"; import {DataProvider} from "../src/DataProvider.sol"; -import {IFunctionsSubscriptions} from "lib/foundry-chainlink-toolkit/src/interfaces/functions/IFunctionsSubscriptions.sol"; +import {IFunctionsSubscriptions} from + "lib/foundry-chainlink-toolkit/src/interfaces/functions/IFunctionsSubscriptions.sol"; import {ILinkToken} from "src/interfaces/ILinkToken.sol"; /** @@ -16,7 +17,6 @@ import {ILinkToken} from "src/interfaces/ILinkToken.sol"; * Deploy the allowlist with the functions coordinator address * Update the allowlists and deploy a coordinator + set the don public keys. */ - contract FunctionsScript is Script, HelperConfig { uint256 public constant DEFAULT_SUB_TOPUP = 1000000000000000000; @@ -27,7 +27,14 @@ contract FunctionsScript is Script, HelperConfig { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); consumer = deployConsumer(); subscriptionId = createSubscription(); + string memory jsonString = string( + abi.encodePacked( + '{"consumer":"', vm.toString(consumer), '","subscriptionId":', vm.toString(subscriptionId), "}" + ) + ); + vm.writeFile("don-reports/donDetails.json", jsonString); console.log("Subscription ID: %s", subscriptionId); + console.log("Consumer: %s", consumer); addConsumerToSubscription(subscriptionId, consumer); fundSubscription(subscriptionId, DEFAULT_SUB_TOPUP); @@ -35,35 +42,24 @@ contract FunctionsScript is Script, HelperConfig { } function deployConsumer() public returns (address) { - DataProvider dataProvider = new DataProvider( - getNetworkConfig().router, - getNetworkConfig().donId - ); + DataProvider dataProvider = new DataProvider(getNetworkConfig().router, getNetworkConfig().donId); return address(dataProvider); } + function createSubscription() public returns (uint64) { - IFunctionsSubscriptions functionsScript = IFunctionsSubscriptions( - getNetworkConfig().router - ); + IFunctionsSubscriptions functionsScript = IFunctionsSubscriptions(getNetworkConfig().router); subscriptionId = functionsScript.createSubscription(); return subscriptionId; } - function addConsumerToSubscription( - uint64 subscriptionId, - address consumer - ) public { - IFunctionsSubscriptions functionsScript = IFunctionsSubscriptions( - getNetworkConfig().router - ); + + function addConsumerToSubscription(uint64 subscriptionId, address consumer) public { + IFunctionsSubscriptions functionsScript = IFunctionsSubscriptions(getNetworkConfig().router); functionsScript.addConsumer(subscriptionId, consumer); } + function fundSubscription(uint64 subscriptionId, uint256 amount) public { ILinkToken linkToken = ILinkToken(getNetworkConfig().linkToken); - linkToken.transferAndCall( - getNetworkConfig().router, - amount, - abi.encode(subscriptionId) - ); + linkToken.transferAndCall(getNetworkConfig().router, amount, abi.encode(subscriptionId)); } function getConsumer() public view returns (address) { @@ -73,9 +69,4 @@ contract FunctionsScript is Script, HelperConfig { function getSubscriptionId() public view returns (uint64) { return subscriptionId; } - - function sendRequest() public { - DataProvider dataProvider = DataProvider(consumer); - dataProvider.sendRequest(subscriptionId); - } } diff --git a/script/HelperConfig.s.sol b/script/HelperConfig.s.sol index 808b61d..e9e0833 100644 --- a/script/HelperConfig.s.sol +++ b/script/HelperConfig.s.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.24; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {MockUSDT} from "../test/mocks/MockUSDT.sol"; +import {MockUSDT} from "../src/MockUSDT.sol"; contract HelperConfig is Script { // If we are on a local chain, we deploy the mock contract @@ -32,11 +32,7 @@ contract HelperConfig is Script { } } - function getSepoliaEthConfig() - public - pure - returns (NetworkConfig memory sepoliaConfig) - { + function getSepoliaEthConfig() public pure returns (NetworkConfig memory sepoliaConfig) { sepoliaConfig = NetworkConfig({ linkToken: 0x779877A7B0D9E8603169DdbD7836e478b4624789, usdToken: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419, @@ -49,11 +45,9 @@ contract HelperConfig is Script { return sepoliaConfig; } - function getAnvilEthConfig() - public - returns (NetworkConfig memory anvilConfig) - { + function getAnvilEthConfig() public returns (NetworkConfig memory anvilConfig) { MockUSDT usdt = new MockUSDT(); + anvilConfig = NetworkConfig({ linkToken: vm.envAddress("LINK_TOKEN_ADDRESS"), usdToken: address(usdt), @@ -67,12 +61,11 @@ contract HelperConfig is Script { } function getNetworkConfig() public view returns (NetworkConfig memory) { + console.log("Active network config:", activeNetworkConfig.usdToken); return activeNetworkConfig; } - function stringToBytes32( - string memory source - ) public pure returns (bytes32 result) { + function stringToBytes32(string memory source) public pure returns (bytes32 result) { bytes memory tempEmptyStringTest = bytes(source); if (tempEmptyStringTest.length == 0) { return 0x0; diff --git a/script/Interactions.s.sol b/script/Interactions.s.sol index e15e91a..bbac48b 100644 --- a/script/Interactions.s.sol +++ b/script/Interactions.s.sol @@ -10,25 +10,30 @@ import {DevOpsTools} from "lib/foundry-devops/src/DevOpsTools.sol"; contract Interactions is Script, HelperConfig { IDataProvider public dataProvider; - address public consumer; + address contractAddress = DevOpsTools.get_most_recent_deployment("DataProvider", block.chainid); - function run() public { + function getLastResponse() public returns (uint256) { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - address contractAddress = DevOpsTools.get_most_recent_deployment( - "DataProvider", - block.chainid - ); dataProvider = IDataProvider(contractAddress); - // sendRequest(); - console.log(bytesToUint(getLastResponse())); + bytes memory response = dataProvider.getLastResponse(); + return abi.decode(response, (uint256)); vm.stopBroadcast(); } - function getLastResponse() public view returns (bytes memory) { - return dataProvider.getLastResponse(); + function sendRequest() public { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + dataProvider = IDataProvider(contractAddress); + // Source code for the DON + string memory source = vm.readFile("./don-simulator/src/source/source.js"); + dataProvider.sendRequest(1, source); + vm.stopBroadcast(); } - function sendRequest() public { - dataProvider.sendRequest(1); + function getLastError() public returns (bytes memory) { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + dataProvider = IDataProvider(contractAddress); + bytes memory lastError = dataProvider.getLastError(); + return lastError; + vm.stopBroadcast(); } } diff --git a/script/VaultInteractions.s.sol b/script/VaultInteractions.s.sol new file mode 100644 index 0000000..9d80ba5 --- /dev/null +++ b/script/VaultInteractions.s.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Script, console} from "forge-std/Script.sol"; +import {HelperConfig} from "./HelperConfig.s.sol"; +import {IDataProvider} from "../src/interfaces/IDataProvider.sol"; +import {FunctionsScript} from "./FunctionsScript.s.sol"; +import {Vault} from "../src/Vault.sol"; +import {DevOpsTools} from "lib/foundry-devops/src/DevOpsTools.sol"; + +contract VaultInteractions is Script, HelperConfig { + IDataProvider public dataProvider; + address contractAddress = DevOpsTools.get_most_recent_deployment("Vault", block.chainid); + + Vault public vault = Vault(contractAddress); + + function depositCollateral() public { + vm.startBroadcast(); + vault.depositCollateral(100); + vm.stopBroadcast(); + } +} diff --git a/src/DataProvider.sol b/src/DataProvider.sol index 7c95b7c..b266712 100644 --- a/src/DataProvider.sol +++ b/src/DataProvider.sol @@ -16,42 +16,27 @@ contract DataProvider is ConfirmedOwner, FunctionsClient { bytes32 public s_lastRequestId; bytes public s_lastResponse; bytes public s_lastError; - string public s_aggregationLogic = "return Functions.encodeUint256(12500);"; bytes32 private immutable i_donId; uint32 private constant GAS_LIMIT = 300000; - constructor( - address _router, - bytes32 _donId - ) FunctionsClient(_router) ConfirmedOwner(msg.sender) { + constructor(address _router, bytes32 _donId) FunctionsClient(_router) ConfirmedOwner(msg.sender) { i_donId = _donId; } - function sendRequest( - uint64 subscriptionId - ) external onlyOwner returns (bytes32 requestId) { + function sendRequest(uint64 subscriptionId, string memory sourceCode) + external + onlyOwner + returns (bytes32 requestId) + { FunctionsRequest.Request memory req; - req.initializeRequest( - FunctionsRequest.Location.Inline, - FunctionsRequest.CodeLanguage.JavaScript, - s_aggregationLogic - ); - - s_lastRequestId = _sendRequest( - req.encodeCBOR(), - subscriptionId, - GAS_LIMIT, - i_donId - ); + req.initializeRequestForInlineJavaScript(sourceCode); + + s_lastRequestId = _sendRequest(req.encodeCBOR(), subscriptionId, GAS_LIMIT, i_donId); return s_lastRequestId; } - function fulfillRequest( - bytes32 requestId, - bytes memory response, - bytes memory err - ) internal override { + function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) internal override { if (s_lastRequestId != requestId) { revert UnexpectedRequestID(requestId); } diff --git a/test/mocks/MockUSDT.sol b/src/MockUSDT.sol similarity index 78% rename from test/mocks/MockUSDT.sol rename to src/MockUSDT.sol index 911020b..be41a34 100644 --- a/test/mocks/MockUSDT.sol +++ b/src/MockUSDT.sol @@ -6,8 +6,11 @@ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract MockUSDT is ERC20 { // USDT uses 6 decimals uint8 private constant DECIMALS = 6; + uint256 private constant INITIAL_SUPPLY = 1000e18; - constructor() ERC20("Mock USDT", "USDT") {} + constructor() ERC20("Mock USDT", "USDT") { + _mint(msg.sender, 100000000000000000000); + } function decimals() public pure override returns (uint8) { return DECIMALS; diff --git a/src/Vault.sol b/src/Vault.sol index 46a8613..f7a1a18 100644 --- a/src/Vault.sol +++ b/src/Vault.sol @@ -5,12 +5,14 @@ pragma solidity ^0.8.24; import {IDataProvider} from "./interfaces/IDataProvider.sol"; import {MADT} from "./MADT.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {console} from "forge-std/console.sol"; contract Vault { error Vault__InvalidAmount(); error Vault__TransferFailed(); error Vault__UserInsufficientBalance(); error Vault__VaultInsufficientBalance(); + error Vault__ApproveFailed(); event CollateralDeposited(address destination, uint256 amount); event CollateralRedeemed(address destination, uint256 amount); @@ -33,14 +35,13 @@ contract Vault { @return bool True if the collateral was deposited successfully */ - function depositCollateral(uint256 amount) public payable returns (bool) { + function depositCollateral(uint256 amountInUsd) public payable returns (bool) { uint256 madValue = dataProvider.getMADValueInUSD(); - uint256 madAmount = (amount * 1e18) / madValue; - - bool success = usdt.transferFrom(msg.sender, address(this), amount); + uint256 madAmount = (amountInUsd * 1e18) / madValue; + bool success = usdt.transferFrom(msg.sender, address(this), amountInUsd * (10 ** 6)); if (!success) revert Vault__TransferFailed(); - emit CollateralDeposited(msg.sender, amount); + emit CollateralDeposited(msg.sender, amountInUsd * (10 ** 6)); madt.mint(msg.sender, madAmount); emit MADTMinted(msg.sender, madAmount); return success; @@ -53,24 +54,24 @@ contract Vault { @return bool True if the collateral was redeemed successfully */ - function redeemCollateral(uint256 amount) public payable returns (bool) { + function redeemCollateral(uint256 amountInUsd) public payable returns (bool) { uint256 madValue = dataProvider.getMADValueInUSD(); - uint256 usdAmount = amount * madValue; + uint256 madAmount = (amountInUsd * 1e18) / madValue; - if (madt.balanceOf(msg.sender) < amount) { + if (madt.balanceOf(msg.sender) < madAmount) { revert Vault__UserInsufficientBalance(); } - if (usdt.balanceOf(address(this)) < usdAmount) { + if (usdt.balanceOf(address(this)) < amountInUsd * 1e6) { revert Vault__VaultInsufficientBalance(); } - madt.burn(msg.sender, amount); + madt.burn(msg.sender, madAmount); - bool success = usdt.transfer(msg.sender, usdAmount); + bool success = usdt.transfer(msg.sender, amountInUsd * 1e6); if (!success) revert Vault__TransferFailed(); - emit CollateralRedeemed(msg.sender, amount); + emit CollateralRedeemed(msg.sender, amountInUsd); return success; } } diff --git a/src/interfaces/IDataProvider.sol b/src/interfaces/IDataProvider.sol index cdc1a73..a0c14e3 100644 --- a/src/interfaces/IDataProvider.sol +++ b/src/interfaces/IDataProvider.sol @@ -3,17 +3,10 @@ pragma solidity ^0.8.24; interface IDataProvider { - function sendRequest( - uint64 subscriptionId - ) external returns (bytes32 requestId); - + function sendRequest(uint64 subscriptionId, string memory sourceCode) external returns (bytes32 requestId); function getMADValueInUSD() external returns (uint256); function getLastResponse() external view returns (bytes memory); function getLastError() external view returns (bytes memory); function getLastRequestId() external view returns (bytes32); - function fulfillRequest( - bytes32 requestId, - bytes memory response, - bytes memory err - ) external; + function fulfillRequest(bytes32 requestId, bytes memory response, bytes memory err) external; } diff --git a/src/interfaces/ILinkToken.sol b/src/interfaces/ILinkToken.sol index e39e336..e10c7ab 100644 --- a/src/interfaces/ILinkToken.sol +++ b/src/interfaces/ILinkToken.sol @@ -2,40 +2,17 @@ pragma solidity ^0.8.24; interface ILinkToken { - function allowance( - address owner, - address spender - ) external view returns (uint256 remaining); - function approve( - address spender, - uint256 value - ) external returns (bool success); + function allowance(address owner, address spender) external view returns (uint256 remaining); + function approve(address spender, uint256 value) external returns (bool success); function balanceOf(address owner) external view returns (uint256 balance); function decimals() external view returns (uint8 decimalPlaces); - function decreaseApproval( - address spender, - uint256 addedValue - ) external returns (bool success); - function increaseApproval( - address spender, - uint256 subtractedValue - ) external; + function decreaseApproval(address spender, uint256 addedValue) external returns (bool success); + function increaseApproval(address spender, uint256 subtractedValue) external; function name() external view returns (string memory tokenName); function symbol() external view returns (string memory tokenSymbol); function totalSupply() external view returns (uint256 totalTokensIssued); - function transfer( - address to, - uint256 value - ) external returns (bool success); - function transferAndCall( - address to, - uint256 value, - bytes calldata data - ) external returns (bool success); - function transferFrom( - address from, - address to, - uint256 value - ) external returns (bool success); + function transfer(address to, uint256 value) external returns (bool success); + function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool success); + function transferFrom(address from, address to, uint256 value) external returns (bool success); function mint(address to, uint256 value) external; } diff --git a/test/VaultTest.t.sol b/test/VaultTest.t.sol index 9d6378b..ac98a40 100644 --- a/test/VaultTest.t.sol +++ b/test/VaultTest.t.sol @@ -4,27 +4,38 @@ pragma solidity ^0.8.24; import {Test} from "forge-std/Test.sol"; import {console} from "forge-std/console.sol"; import {StdCheats} from "forge-std/StdCheats.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import {FunctionsAuto} from "../script/FunctionsAuto.s.sol"; +import {FunctionsScript} from "../script/FunctionsScript.s.sol"; import {IDataProvider} from "../src/interfaces/IDataProvider.sol"; import {HelperConfig} from "../script/HelperConfig.s.sol"; +import {Vault} from "../src/Vault.sol"; +import {MADT} from "../src/MADT.sol"; +import {DevOpsTools} from "lib/foundry-devops/src/DevOpsTools.sol"; contract VaultTest is Test, HelperConfig { - FunctionsAuto functionsAuto; + FunctionsScript functionsScript; address public consumer; uint64 public subscriptionId; IDataProvider public dataProvider; - + Vault public vault; + MADT public madt; + IERC20 public usdt; address public deployer = makeAddr("deployer"); string public MAINNET_RPC_URL = vm.envString("RPC_URL"); function setUp() public { - functionsAuto = new FunctionsAuto(); - functionsAuto.run(); - consumer = functionsAuto.getConsumer(); - subscriptionId = functionsAuto.getSubscriptionId(); + address contractAddress = DevOpsTools.get_most_recent_deployment("DataProvider", block.chainid); + + dataProvider = IDataProvider(contractAddress); + usdt = IERC20(getNetworkConfig().usdToken); + madt = new MADT(); + vault = new Vault(dataProvider, madt, usdt); + madt.setVault(address(vault)); - dataProvider = IDataProvider(consumer); + deal(address(usdt), deployer, 100000000000000000000); + vm.prank(deployer); + usdt.approve(address(vault), 100000000000000000000); } /* @@ -35,23 +46,61 @@ contract VaultTest is Test, HelperConfig { * - Test the case in which the USD/MAD conversion drops between the time of deposit and redemption */ - function testSendConsumerRequest() public { + function testIfMADValueIsCorrectlyReturned() public { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - bytes32 requestId = dataProvider.sendRequest(subscriptionId); - console.logBytes32(requestId); - bytes memory response = dataProvider.getLastResponse(); - console.logBytes(response); + uint256 response = dataProvider.getMADValueInUSD(); vm.stopBroadcast(); } - function testIfConsumerRequestIsReceived() public { - vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - bytes32 requestId = dataProvider.sendRequest(subscriptionId); - vm.stopBroadcast(); + function testIfCollateralDepositWorks() public { + uint256 startingBalance = usdt.balanceOf(deployer); + uint256 amountInUsdToDeposit = 100; + uint256 madValue = dataProvider.getMADValueInUSD(); + + vm.prank(deployer); + vault.depositCollateral(amountInUsdToDeposit); + + assertEq(usdt.balanceOf(address(vault)), amountInUsdToDeposit * 1e6); + assertEq(madt.balanceOf(deployer), (amountInUsdToDeposit * 1e18) / madValue); + assertEq(usdt.balanceOf(deployer), startingBalance - amountInUsdToDeposit * 1e6); + } + + function testIfCollateralRedemptionWorks() public { + uint256 startingBalance = usdt.balanceOf(deployer); + + vm.prank(deployer); + vault.depositCollateral(100); + + vm.prank(deployer); + vault.redeemCollateral(100); + + assertEq(usdt.balanceOf(deployer), startingBalance); + assertEq(usdt.balanceOf(address(vault)), 0); + assertEq(madt.balanceOf(deployer), 0); + } + + function testIfVaultCannotRedeemMoreThanDeposited() public { + vm.prank(deployer); + vault.depositCollateral(100); + + vm.prank(deployer); + vm.expectRevert(Vault.Vault__UserInsufficientBalance.selector); + vault.redeemCollateral(101); + } + + function testIfVaultCannotRedeemMoreThanItHolds() public { + vm.prank(deployer); + vault.depositCollateral(100); + + uint256 vaultBalance = usdt.balanceOf(address(vault)); + vm.prank(address(vault)); + usdt.transfer(address(1), vaultBalance - 50e6); - vm.selectFork(0); + vm.prank(deployer); + vm.expectRevert(Vault.Vault__VaultInsufficientBalance.selector); + vault.redeemCollateral(100); - bytes memory response = dataProvider.getLastResponse(); - console.logBytes(response); + vm.prank(deployer); + vault.redeemCollateral(50); } } diff --git a/test/mocks/LinkToken.sol b/test/mocks/LinkToken.sol index fd04e98..a9ef30d 100644 --- a/test/mocks/LinkToken.sol +++ b/test/mocks/LinkToken.sol @@ -5,11 +5,7 @@ pragma solidity ^0.8.0; import {ERC20} from "@solmate/tokens/ERC20.sol"; interface ERC677Receiver { - function onTokenTransfer( - address _sender, - uint256 _value, - bytes memory _data - ) external; + function onTokenTransfer(address _sender, uint256 _value, bytes memory _data) external; } contract LinkToken is ERC20 { @@ -24,12 +20,7 @@ contract LinkToken is ERC20 { _mint(to, value); } - event Transfer( - address indexed from, - address indexed to, - uint256 value, - bytes data - ); + event Transfer(address indexed from, address indexed to, uint256 value, bytes data); /** * @dev transfer token to a contract address with additional data if the recipient is a contact. @@ -37,11 +28,7 @@ contract LinkToken is ERC20 { * @param _value The amount to be transferred. * @param _data The extra data to be passed to the receiving contract. */ - function transferAndCall( - address _to, - uint256 _value, - bytes memory _data - ) public virtual returns (bool success) { + function transferAndCall(address _to, uint256 _value, bytes memory _data) public virtual returns (bool success) { super.transfer(_to, _value); // emit Transfer(msg.sender, _to, _value, _data); emit Transfer(msg.sender, _to, _value, _data); @@ -53,11 +40,7 @@ contract LinkToken is ERC20 { // PRIVATE - function contractFallback( - address _to, - uint256 _value, - bytes memory _data - ) private { + function contractFallback(address _to, uint256 _value, bytes memory _data) private { ERC677Receiver receiver = ERC677Receiver(_to); receiver.onTokenTransfer(msg.sender, _value, _data); }