From 194916088735cffc276cee8146c75631eb35fad3 Mon Sep 17 00:00:00 2001 From: Imad Archid Date: Wed, 19 Feb 2025 16:03:45 +0100 Subject: [PATCH 1/6] feat(scripts): adding more scripts + fixing mock USDT issue --- Makefile | 13 ++++++++----- script/DeployMockUSDT.s.sol | 13 +++++++++++++ script/DeployVault.s.sol | 2 +- script/HelperConfig.s.sol | 22 ++++++++++++++++------ script/VaultInteractions.s.sol | 15 ++++++++++++--- 5 files changed, 50 insertions(+), 15 deletions(-) create mode 100644 script/DeployMockUSDT.s.sol diff --git a/Makefile b/Makefile index 0b12a7a..897cddb 100644 --- a/Makefile +++ b/Makefile @@ -38,10 +38,10 @@ setup-functions: # 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 + forge script script/Interactions.s.sol:Interactions --sig "sendRequest()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vv get-last-response: - forge script script/Interactions.s.sol:Interactions --sig "getLastResponse()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vvvvv + forge script script/Interactions.s.sol:Interactions --sig "getLastResponse()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vv get-last-error: cast call $(CONTRACT_ADDRESS) "getLastError()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) @@ -53,17 +53,20 @@ get-last-request-id: deploy-madt: forge script script/DeployMADT.s.sol:DeployMADT --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast +deploy-usdt: + forge script script/DeployMockUSDT.s.sol:DeployMockUSDT --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 + forge script script/DeployVault.s.sol:DeployVault --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vv # 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 + forge script script/VaultInteractions.s.sol:VaultInteractions --sig "depositCollateral()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vv # 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 + forge script script/VaultInteractions.s.sol:VaultInteractions --sig "redeemCollateral(uint256)" $(AMOUNT) --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vv NETWORK_ARGS := --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast diff --git a/script/DeployMockUSDT.s.sol b/script/DeployMockUSDT.s.sol new file mode 100644 index 0000000..4ecdd14 --- /dev/null +++ b/script/DeployMockUSDT.s.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.24; + +import {Script} from "forge-std/Script.sol"; +import {MockUSDT} from "../src/MockUSDT.sol"; + +contract DeployMockUSDT is Script { + function run() public { + vm.startBroadcast(vm.envUint("PRIVATE_KEY")); + new MockUSDT(); + vm.stopBroadcast(); + } +} diff --git a/script/DeployVault.s.sol b/script/DeployVault.s.sol index 51d9756..8719d4a 100644 --- a/script/DeployVault.s.sol +++ b/script/DeployVault.s.sol @@ -22,7 +22,7 @@ contract DeployVault is Script, HelperConfig { dataProvider = IDataProvider(contractAddress); IERC20 usdt = IERC20(getNetworkConfig().usdToken); Vault vault = new Vault(dataProvider, madt, usdt); - // madt.setVault(address(vault)); + madt.setVault(address(vault)); vm.stopBroadcast(); } } diff --git a/script/HelperConfig.s.sol b/script/HelperConfig.s.sol index e9e0833..ae76fa9 100644 --- a/script/HelperConfig.s.sol +++ b/script/HelperConfig.s.sol @@ -5,12 +5,15 @@ import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; import {MockUSDT} from "../src/MockUSDT.sol"; +import {DevOpsTools} from "lib/foundry-devops/src/DevOpsTools.sol"; contract HelperConfig is Script { // If we are on a local chain, we deploy the mock contract // Otherwise, we fetch the existing address from the live network uint256 public constant ETH_SEPOLIA_CHAIN_ID = 11155111; + address public usdtAddress = + DevOpsTools.get_most_recent_deployment("MockUSDT", block.chainid); struct NetworkConfig { address linkToken; @@ -32,7 +35,11 @@ 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, @@ -45,12 +52,13 @@ contract HelperConfig is Script { return sepoliaConfig; } - function getAnvilEthConfig() public returns (NetworkConfig memory anvilConfig) { - MockUSDT usdt = new MockUSDT(); - + function getAnvilEthConfig() + public + returns (NetworkConfig memory anvilConfig) + { anvilConfig = NetworkConfig({ linkToken: vm.envAddress("LINK_TOKEN_ADDRESS"), - usdToken: address(usdt), + usdToken: usdtAddress, subscriptionId: 1, donId: stringToBytes32(vm.envString("DON_ID")), router: vm.envAddress("FUNCTIONS_ROUTER_ADDRESS"), @@ -65,7 +73,9 @@ contract HelperConfig is Script { 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/VaultInteractions.s.sol b/script/VaultInteractions.s.sol index 9d80ba5..d7c85c3 100644 --- a/script/VaultInteractions.s.sol +++ b/script/VaultInteractions.s.sol @@ -7,16 +7,25 @@ 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"; - +import {MockUSDT} from "../src/MockUSDT.sol"; contract VaultInteractions is Script, HelperConfig { IDataProvider public dataProvider; - address contractAddress = DevOpsTools.get_most_recent_deployment("Vault", block.chainid); + address contractAddress = + DevOpsTools.get_most_recent_deployment("Vault", block.chainid); Vault public vault = Vault(contractAddress); + MockUSDT public usdt = MockUSDT(usdtAddress); function depositCollateral() public { vm.startBroadcast(); - vault.depositCollateral(100); + usdt.approve(contractAddress, 100000000000000000000000000000); + vault.depositCollateral(12500); + vm.stopBroadcast(); + } + + function redeemCollateral(uint256 amountInUsd) public { + vm.startBroadcast(); + vault.redeemCollateral(amountInUsd); vm.stopBroadcast(); } } From 6ed2c5010de9aeba11dd172c7d07e1c224117dde Mon Sep 17 00:00:00 2001 From: Imad Archid Date: Wed, 19 Feb 2025 16:26:42 +0100 Subject: [PATCH 2/6] fix(madt): fix scaling issue --- Makefile | 7 +++++++ script/HelperConfig.s.sol | 18 ++++-------------- script/VaultInteractions.s.sol | 4 ++-- src/MADT.sol | 4 ++++ src/MockUSDT.sol | 2 +- src/Vault.sol | 8 ++++---- 6 files changed, 22 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 897cddb..2dd4aa0 100644 --- a/Makefile +++ b/Makefile @@ -36,6 +36,13 @@ simulate-don: setup-functions: forge script script/FunctionsScript.s.sol:FunctionsScript --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast +deploy-all: + make deploy-madt && \ + make deploy-usdt && \ + make setup-functions && \ + make send-request && \ + make deploy-vault + # 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 -vv diff --git a/script/HelperConfig.s.sol b/script/HelperConfig.s.sol index ae76fa9..9799f25 100644 --- a/script/HelperConfig.s.sol +++ b/script/HelperConfig.s.sol @@ -12,8 +12,7 @@ contract HelperConfig is Script { // Otherwise, we fetch the existing address from the live network uint256 public constant ETH_SEPOLIA_CHAIN_ID = 11155111; - address public usdtAddress = - DevOpsTools.get_most_recent_deployment("MockUSDT", block.chainid); + address public usdtAddress = DevOpsTools.get_most_recent_deployment("MockUSDT", block.chainid); struct NetworkConfig { address linkToken; @@ -35,11 +34,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, @@ -52,10 +47,7 @@ contract HelperConfig is Script { return sepoliaConfig; } - function getAnvilEthConfig() - public - returns (NetworkConfig memory anvilConfig) - { + function getAnvilEthConfig() public returns (NetworkConfig memory anvilConfig) { anvilConfig = NetworkConfig({ linkToken: vm.envAddress("LINK_TOKEN_ADDRESS"), usdToken: usdtAddress, @@ -73,9 +65,7 @@ contract HelperConfig is Script { 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/VaultInteractions.s.sol b/script/VaultInteractions.s.sol index d7c85c3..c36635b 100644 --- a/script/VaultInteractions.s.sol +++ b/script/VaultInteractions.s.sol @@ -8,10 +8,10 @@ import {FunctionsScript} from "./FunctionsScript.s.sol"; import {Vault} from "../src/Vault.sol"; import {DevOpsTools} from "lib/foundry-devops/src/DevOpsTools.sol"; import {MockUSDT} from "../src/MockUSDT.sol"; + contract VaultInteractions is Script, HelperConfig { IDataProvider public dataProvider; - address contractAddress = - DevOpsTools.get_most_recent_deployment("Vault", block.chainid); + address contractAddress = DevOpsTools.get_most_recent_deployment("Vault", block.chainid); Vault public vault = Vault(contractAddress); MockUSDT public usdt = MockUSDT(usdtAddress); diff --git a/src/MADT.sol b/src/MADT.sol index 0c876f5..12f1174 100644 --- a/src/MADT.sol +++ b/src/MADT.sol @@ -30,4 +30,8 @@ contract MADT is ERC20, Ownable { function burn(address from, uint256 amount) public onlyVault { _burn(from, amount); } + + function decimals() public pure override returns (uint8) { + return 6; + } } diff --git a/src/MockUSDT.sol b/src/MockUSDT.sol index be41a34..5b13486 100644 --- a/src/MockUSDT.sol +++ b/src/MockUSDT.sol @@ -9,7 +9,7 @@ contract MockUSDT is ERC20 { uint256 private constant INITIAL_SUPPLY = 1000e18; constructor() ERC20("Mock USDT", "USDT") { - _mint(msg.sender, 100000000000000000000); + _mint(msg.sender, 100000000000); } function decimals() public pure override returns (uint8) { diff --git a/src/Vault.sol b/src/Vault.sol index f7a1a18..82d1530 100644 --- a/src/Vault.sol +++ b/src/Vault.sol @@ -37,7 +37,7 @@ contract Vault { function depositCollateral(uint256 amountInUsd) public payable returns (bool) { uint256 madValue = dataProvider.getMADValueInUSD(); - uint256 madAmount = (amountInUsd * 1e18) / madValue; + uint256 madAmount = (amountInUsd * (10 ** 8)) / madValue; bool success = usdt.transferFrom(msg.sender, address(this), amountInUsd * (10 ** 6)); if (!success) revert Vault__TransferFailed(); @@ -56,19 +56,19 @@ contract Vault { function redeemCollateral(uint256 amountInUsd) public payable returns (bool) { uint256 madValue = dataProvider.getMADValueInUSD(); - uint256 madAmount = (amountInUsd * 1e18) / madValue; + uint256 madAmount = (amountInUsd * (10 ** 8)) / madValue; if (madt.balanceOf(msg.sender) < madAmount) { revert Vault__UserInsufficientBalance(); } - if (usdt.balanceOf(address(this)) < amountInUsd * 1e6) { + if (usdt.balanceOf(address(this)) < amountInUsd * (10 ** 6)) { revert Vault__VaultInsufficientBalance(); } madt.burn(msg.sender, madAmount); - bool success = usdt.transfer(msg.sender, amountInUsd * 1e6); + bool success = usdt.transfer(msg.sender, amountInUsd * (10 ** 6)); if (!success) revert Vault__TransferFailed(); emit CollateralRedeemed(msg.sender, amountInUsd); From 04cded5cab6391c8f30e299f5a21daa630edb569 Mon Sep 17 00:00:00 2001 From: Imad Archid Date: Wed, 19 Feb 2025 17:17:14 +0100 Subject: [PATCH 3/6] feat(don-simulator): allow secrets to be passed to execution --- don-simulator/package-lock.json | 13 +++++++++ don-simulator/package.json | 1 + don-simulator/src/config.js | 33 ++++++++++++++++++++++ don-simulator/src/localFunctionsTestnet.ts | 7 ++++- don-simulator/src/source/source.js | 7 +++-- 5 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 don-simulator/src/config.js diff --git a/don-simulator/package-lock.json b/don-simulator/package-lock.json index 913a0e0..86f9167 100644 --- a/don-simulator/package-lock.json +++ b/don-simulator/package-lock.json @@ -6,6 +6,7 @@ "": { "dependencies": { "cbor": "^10.0.3", + "dotenv": "^16.4.7", "ethers": "^5.7.2" }, "devDependencies": { @@ -759,6 +760,18 @@ "node": ">=18" } }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/elliptic": { "version": "6.5.4", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", diff --git a/don-simulator/package.json b/don-simulator/package.json index d720980..dd4910d 100644 --- a/don-simulator/package.json +++ b/don-simulator/package.json @@ -4,6 +4,7 @@ }, "dependencies": { "cbor": "^10.0.3", + "dotenv": "^16.4.7", "ethers": "^5.7.2" } } diff --git a/don-simulator/src/config.js b/don-simulator/src/config.js new file mode 100644 index 0000000..2bb2bd1 --- /dev/null +++ b/don-simulator/src/config.js @@ -0,0 +1,33 @@ +require("dotenv").config(); +const Location = { + Inline: 0, + Remote: 1, + DONHosted: 2, +}; + +const CodeLanguage = { + JavaScript: 0, +}; + +const ReturnType = { + uint: "uint256", + uint256: "uint256", + int: "int256", + int256: "int256", + string: "string", + bytes: "bytes", +}; + +// Configure the request by setting the fields below +const requestConfig = { + // Location of source code (only Inline is currently supported) + codeLocation: Location.Inline, + // Optional. Secrets can be accessed within the source code with `secrets.varName` (ie: secrets.apiKey). The secrets object can only contain string values. + secrets: { apiKey: process.env.RANDOM_ENV ?? "salam2" }, + // Code language (only JavaScript is currently supported) + codeLanguage: CodeLanguage.JavaScript, + // Expected type of the returned value + expectedReturnType: ReturnType.string, +}; + +module.exports = requestConfig; diff --git a/don-simulator/src/localFunctionsTestnet.ts b/don-simulator/src/localFunctionsTestnet.ts index 376be67..f84ac9b 100644 --- a/don-simulator/src/localFunctionsTestnet.ts +++ b/don-simulator/src/localFunctionsTestnet.ts @@ -31,6 +31,7 @@ import type { FunctionsContracts, RequestEventData, } from "./types"; +import path from "path"; export const startLocalFunctionsTestnet = async ( coordinatorAddress?: string, @@ -494,8 +495,12 @@ if (require.main === module) { try { const args = process.argv.slice(2); const coordinatorAddress = args[0]; + const configPath = path.join(process.cwd(), "don-simulator/src/config.js"); console.log("Starting local Functions testnet..."); - const testnet = await startLocalFunctionsTestnet(coordinatorAddress); + const testnet = await startLocalFunctionsTestnet( + coordinatorAddress, + configPath + ); console.log("Local Functions testnet started successfully"); // Keep the process running diff --git a/don-simulator/src/source/source.js b/don-simulator/src/source/source.js index 00dec2e..2cbbae3 100644 --- a/don-simulator/src/source/source.js +++ b/don-simulator/src/source/source.js @@ -1,2 +1,5 @@ -return Functions.encodeUint256(0.1 * 100); - \ No newline at end of file +if (secrets.apiKey === "salam") { + return Functions.encodeUint256(0.4 * 100); +} else { + return Functions.encodeUint256(0.6 * 100); +} From bded3524e07206ccabf7d93e032bb67db0459548 Mon Sep 17 00:00:00 2001 From: Imad Archid Date: Thu, 20 Feb 2025 09:47:05 +0100 Subject: [PATCH 4/6] feat(don-simulator): aPI calls are now made by the DON --- Makefile | 2 +- don-simulator/src/config.js | 7 ++- don-simulator/src/localFunctionsTestnet.ts | 5 ++- don-simulator/src/source/source.js | 52 ++++++++++++++++++++-- don-simulator/src/test.js | 3 ++ script/DeployVault.s.sol | 12 ++--- 6 files changed, 68 insertions(+), 13 deletions(-) create mode 100644 don-simulator/src/test.js diff --git a/Makefile b/Makefile index 2dd4aa0..2bbbf11 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,7 @@ get-last-response: forge script script/Interactions.s.sol:Interactions --sig "getLastResponse()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vv get-last-error: - cast call $(CONTRACT_ADDRESS) "getLastError()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) + forge script script/Interactions.s.sol:Interactions --sig "getLastError()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vv get-last-request-id: cast call $(CONTRACT_ADDRESS) "getLastRequestId()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) diff --git a/don-simulator/src/config.js b/don-simulator/src/config.js index 2bb2bd1..3cc8137 100644 --- a/don-simulator/src/config.js +++ b/don-simulator/src/config.js @@ -23,11 +23,14 @@ const requestConfig = { // Location of source code (only Inline is currently supported) codeLocation: Location.Inline, // Optional. Secrets can be accessed within the source code with `secrets.varName` (ie: secrets.apiKey). The secrets object can only contain string values. - secrets: { apiKey: process.env.RANDOM_ENV ?? "salam2" }, + secrets: { + exchangeRateApiKey: process.env.EXCH_RATE_KEY ?? "", + currencyApiKey: process.env.CURRENCY_KEY ?? "", + }, // Code language (only JavaScript is currently supported) codeLanguage: CodeLanguage.JavaScript, // Expected type of the returned value - expectedReturnType: ReturnType.string, + expectedReturnType: ReturnType.uint256, }; module.exports = requestConfig; diff --git a/don-simulator/src/localFunctionsTestnet.ts b/don-simulator/src/localFunctionsTestnet.ts index f84ac9b..4e5d118 100644 --- a/don-simulator/src/localFunctionsTestnet.ts +++ b/don-simulator/src/localFunctionsTestnet.ts @@ -495,7 +495,10 @@ if (require.main === module) { try { const args = process.argv.slice(2); const coordinatorAddress = args[0]; - const configPath = path.join(process.cwd(), "don-simulator/src/config.js"); + const configPath = path.join( + process.cwd(), + "don-simulator/src/config.js" + ); console.log("Starting local Functions testnet..."); const testnet = await startLocalFunctionsTestnet( coordinatorAddress, diff --git a/don-simulator/src/source/source.js b/don-simulator/src/source/source.js index 2cbbae3..4abdf4f 100644 --- a/don-simulator/src/source/source.js +++ b/don-simulator/src/source/source.js @@ -1,5 +1,51 @@ -if (secrets.apiKey === "salam") { - return Functions.encodeUint256(0.4 * 100); +// Discontinued because of API changes. +// const bkamAPI = Functions.makeHttpRequest({ +// url: `https://api.centralbankofmorocco.ma/cours/Version1/api/CoursVirement?libDevise=USD`, +// headers: { "Ocp-Apim-Subscription-Key": secrets.BKAM_KEY }, +// }) + +if (secrets.exchangeRateApiKey.length === 0) { + throw Error("No valid EXCH_RATE_KEY was supplied"); +} + +if (secrets.currencyApiKey.length === 0) { + throw Error("No valid CURRENCY_KEY was supplied"); +} + +const exchRateAPI = Functions.makeHttpRequest({ + url: `https://v6.exchangerate-api.com/v6/${secrets.exchangeRateApiKey}/pair/MAD/USD`, +}); + +const currencyAPI = Functions.makeHttpRequest({ + url: `https://api.currencyapi.com/v3/latest?apikey=${secrets.currencyApiKey}¤cies=USD&base_currency=MAD`, +}); + +const [exchRateAPIResponse, currencyAPIResponse] = await Promise.all([ + exchRateAPI, + currencyAPI, +]); + +const prices = []; +if (!exchRateAPIResponse.error) { + prices.push(exchRateAPIResponse.data.conversion_rate); } else { - return Functions.encodeUint256(0.6 * 100); + console.log("ExchangeRateAPI Error"); + throw Error(JSON.stringify(exchRateAPIResponse)); +} +if (!currencyAPIResponse.error) { + prices.push(currencyAPIResponse.data.data.USD.value); +} else { + console.log("CurrencyAPI Error"); + throw Error(JSON.stringify(currencyAPIResponse)); +} + +if (prices.length < 2) { + // If an error is thrown, it will be returned back to the smart contract + throw Error("More than 1 API failed"); } + +const medianRate = prices.sort((a, b) => a - b)[Math.round(prices.length / 2)]; +console.log(`Median MAD rate: $${medianRate.toFixed(2)}`); + +return Functions.encodeUint256(Math.round(medianRate * 100)); +// return Functions.encodeUint256(Math.round(Math.random() * 10000)) diff --git a/don-simulator/src/test.js b/don-simulator/src/test.js new file mode 100644 index 0000000..09fda35 --- /dev/null +++ b/don-simulator/src/test.js @@ -0,0 +1,3 @@ +require("dotenv").config(); + +console.log(process.env.EXCH_RATE_KEY); \ No newline at end of file diff --git a/script/DeployVault.s.sol b/script/DeployVault.s.sol index 8719d4a..8d02fde 100644 --- a/script/DeployVault.s.sol +++ b/script/DeployVault.s.sol @@ -11,15 +11,15 @@ 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); + function run() public { + 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); + MADT madt = MADT(MADTAddress); - function run() public { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); - dataProvider = IDataProvider(contractAddress); + console.log("Deploying Vault.."); + IDataProvider dataProvider = IDataProvider(contractAddress); IERC20 usdt = IERC20(getNetworkConfig().usdToken); Vault vault = new Vault(dataProvider, madt, usdt); madt.setVault(address(vault)); From c3b2c2b4f2f2c46bbe5cffca4f2f7ee622a04681 Mon Sep 17 00:00:00 2001 From: Imad Archid Date: Thu, 20 Feb 2025 12:10:58 +0100 Subject: [PATCH 5/6] feat(madt): rebase mechanism at the balance level --- Makefile | 8 +- don-simulator/src/localFunctionsTestnet.ts | 22 +++++ don-simulator/src/source/source.js | 102 ++++++++++----------- script/Interactions.s.sol | 4 +- script/VaultInteractions.s.sol | 6 ++ src/MADT.sol | 34 +++++++ src/Vault.sol | 19 +++- 7 files changed, 137 insertions(+), 58 deletions(-) diff --git a/Makefile b/Makefile index 2bbbf11..f7d4851 100644 --- a/Makefile +++ b/Makefile @@ -40,8 +40,8 @@ deploy-all: make deploy-madt && \ make deploy-usdt && \ make setup-functions && \ - make send-request && \ - make deploy-vault + make deploy-vault && \ + make send-request # Interactions with the DON via the DataProvider contract send-request: @@ -75,6 +75,10 @@ deposit-collateral: redeem-collateral: forge script script/VaultInteractions.s.sol:VaultInteractions --sig "redeemCollateral(uint256)" $(AMOUNT) --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vv +# Rebase +rebase: + forge script script/VaultInteractions.s.sol:VaultInteractions --sig "rebase()" --rpc-url http://localhost:8545 --private-key $(DEFAULT_ANVIL_KEY) --broadcast -vv + 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/localFunctionsTestnet.ts b/don-simulator/src/localFunctionsTestnet.ts index 4e5d118..5d65a68 100644 --- a/don-simulator/src/localFunctionsTestnet.ts +++ b/don-simulator/src/localFunctionsTestnet.ts @@ -40,6 +40,23 @@ export const startLocalFunctionsTestnet = async ( ): Promise => { const provider = new providers.JsonRpcProvider(`http://localhost:${port}`); + // Add error handler for provider disconnection + provider.on("error", async (error) => { + console.log("Provider error detected, shutting down testnet..."); + // Clean up event listeners and close connections + await close(); + process.exit(1); + }); + + // Add network change handler + provider.on("network", async (newNetwork, oldNetwork) => { + if (oldNetwork) { + console.log("Network connection lost, shutting down testnet..."); + await close(); + process.exit(1); + } + }); + const admin = new Wallet( "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80", provider @@ -101,9 +118,14 @@ export const startLocalFunctionsTestnet = async ( const getFunds: GetFunds = async () => {}; const close = async (): Promise => { + // Remove all event listeners contracts.functionsMockCoordinatorContract.removeAllListeners( "OracleRequest" ); + provider.removeAllListeners(); + + // Optional: Add any additional cleanup needed + console.log("Local Functions testnet shut down successfully"); }; return { diff --git a/don-simulator/src/source/source.js b/don-simulator/src/source/source.js index 4abdf4f..0b71796 100644 --- a/don-simulator/src/source/source.js +++ b/don-simulator/src/source/source.js @@ -1,51 +1,51 @@ -// Discontinued because of API changes. -// const bkamAPI = Functions.makeHttpRequest({ -// url: `https://api.centralbankofmorocco.ma/cours/Version1/api/CoursVirement?libDevise=USD`, -// headers: { "Ocp-Apim-Subscription-Key": secrets.BKAM_KEY }, -// }) - -if (secrets.exchangeRateApiKey.length === 0) { - throw Error("No valid EXCH_RATE_KEY was supplied"); -} - -if (secrets.currencyApiKey.length === 0) { - throw Error("No valid CURRENCY_KEY was supplied"); -} - -const exchRateAPI = Functions.makeHttpRequest({ - url: `https://v6.exchangerate-api.com/v6/${secrets.exchangeRateApiKey}/pair/MAD/USD`, -}); - -const currencyAPI = Functions.makeHttpRequest({ - url: `https://api.currencyapi.com/v3/latest?apikey=${secrets.currencyApiKey}¤cies=USD&base_currency=MAD`, -}); - -const [exchRateAPIResponse, currencyAPIResponse] = await Promise.all([ - exchRateAPI, - currencyAPI, -]); - -const prices = []; -if (!exchRateAPIResponse.error) { - prices.push(exchRateAPIResponse.data.conversion_rate); -} else { - console.log("ExchangeRateAPI Error"); - throw Error(JSON.stringify(exchRateAPIResponse)); -} -if (!currencyAPIResponse.error) { - prices.push(currencyAPIResponse.data.data.USD.value); -} else { - console.log("CurrencyAPI Error"); - throw Error(JSON.stringify(currencyAPIResponse)); -} - -if (prices.length < 2) { - // If an error is thrown, it will be returned back to the smart contract - throw Error("More than 1 API failed"); -} - -const medianRate = prices.sort((a, b) => a - b)[Math.round(prices.length / 2)]; -console.log(`Median MAD rate: $${medianRate.toFixed(2)}`); - -return Functions.encodeUint256(Math.round(medianRate * 100)); -// return Functions.encodeUint256(Math.round(Math.random() * 10000)) +// // Discontinued because of API changes. +// // const bkamAPI = Functions.makeHttpRequest({ +// // url: `https://api.centralbankofmorocco.ma/cours/Version1/api/CoursVirement?libDevise=USD`, +// // headers: { "Ocp-Apim-Subscription-Key": secrets.BKAM_KEY }, +// // }) + +// if (secrets.exchangeRateApiKey.length === 0) { +// throw Error("No valid EXCH_RATE_KEY was supplied"); +// } + +// if (secrets.currencyApiKey.length === 0) { +// throw Error("No valid CURRENCY_KEY was supplied"); +// } + +// const exchRateAPI = Functions.makeHttpRequest({ +// url: `https://v6.exchangerate-api.com/v6/${secrets.exchangeRateApiKey}/pair/MAD/USD`, +// }); + +// const currencyAPI = Functions.makeHttpRequest({ +// url: `https://api.currencyapi.com/v3/latest?apikey=${secrets.currencyApiKey}¤cies=USD&base_currency=MAD`, +// }); + +// const [exchRateAPIResponse, currencyAPIResponse] = await Promise.all([ +// exchRateAPI, +// currencyAPI, +// ]); + +// const prices = []; +// if (!exchRateAPIResponse.error) { +// prices.push(exchRateAPIResponse.data.conversion_rate); +// } else { +// console.log("ExchangeRateAPI Error"); +// throw Error(JSON.stringify(exchRateAPIResponse)); +// } +// if (!currencyAPIResponse.error) { +// prices.push(currencyAPIResponse.data.data.USD.value); +// } else { +// console.log("CurrencyAPI Error"); +// throw Error(JSON.stringify(currencyAPIResponse)); +// } + +// if (prices.length < 2) { +// // If an error is thrown, it will be returned back to the smart contract +// throw Error("More than 1 API failed"); +// } + +// const medianRate = prices.sort((a, b) => a - b)[Math.round(prices.length / 2)]; +// console.log(`Median MAD rate: $${medianRate.toFixed(2)}`); + +// return Functions.encodeUint256(Math.round(medianRate * 100)); +return Functions.encodeUint256(11); diff --git a/script/Interactions.s.sol b/script/Interactions.s.sol index bbac48b..3c6da79 100644 --- a/script/Interactions.s.sol +++ b/script/Interactions.s.sol @@ -15,9 +15,9 @@ contract Interactions is Script, HelperConfig { function getLastResponse() public returns (uint256) { vm.startBroadcast(vm.envUint("PRIVATE_KEY")); dataProvider = IDataProvider(contractAddress); - bytes memory response = dataProvider.getLastResponse(); - return abi.decode(response, (uint256)); + uint256 madValue = dataProvider.getMADValueInUSD(); vm.stopBroadcast(); + return madValue; } function sendRequest() public { diff --git a/script/VaultInteractions.s.sol b/script/VaultInteractions.s.sol index c36635b..4655c22 100644 --- a/script/VaultInteractions.s.sol +++ b/script/VaultInteractions.s.sol @@ -28,4 +28,10 @@ contract VaultInteractions is Script, HelperConfig { vault.redeemCollateral(amountInUsd); vm.stopBroadcast(); } + + function rebase() public { + vm.startBroadcast(); + vault.rebase(); + vm.stopBroadcast(); + } } diff --git a/src/MADT.sol b/src/MADT.sol index 12f1174..21e0c77 100644 --- a/src/MADT.sol +++ b/src/MADT.sol @@ -10,6 +10,7 @@ contract MADT is ERC20, Ownable { error MADT__InvalidVaultAddress(); address public vault; + uint256 public rebaseFactor = 1e18; constructor() ERC20("Tokenized MAD", "MADT") Ownable(msg.sender) {} @@ -31,6 +32,39 @@ contract MADT is ERC20, Ownable { _burn(from, amount); } + function balanceOf(address account) public view override returns (uint256) { + return (super.balanceOf(account) * rebaseFactor) / 1e18; + } + + function transfer( + address to, + uint256 amount + ) public override returns (bool) { + uint256 scaledAmount = (amount * 1e18) / rebaseFactor; + return super.transfer(to, scaledAmount); + } + + function transferFrom( + address from, + address to, + uint256 amount + ) public override returns (bool) { + uint256 scaledAmount = (amount * 1e18) / rebaseFactor; + return super.transferFrom(from, to, scaledAmount); + } + + function rebase(uint256 madtToUSDTPrice) public onlyVault { + require(madtToUSDTPrice > 0, "Invalid MADT to USDT price"); + + // uint256 oldRebaseFactor = rebaseFactor; + uint256 scalingFactor = 100; + + // Calculate the new rebase factor based on the ratio + uint256 newRebaseFactor = (rebaseFactor * madtToUSDTPrice) / scalingFactor; + rebaseFactor = newRebaseFactor; + // emit Rebase(oldRebaseFactor, rebaseFactor); + } + function decimals() public pure override returns (uint8) { return 6; } diff --git a/src/Vault.sol b/src/Vault.sol index 82d1530..a99a1a6 100644 --- a/src/Vault.sol +++ b/src/Vault.sol @@ -35,10 +35,16 @@ contract Vault { @return bool True if the collateral was deposited successfully */ - function depositCollateral(uint256 amountInUsd) public payable returns (bool) { + function depositCollateral( + uint256 amountInUsd + ) public payable returns (bool) { uint256 madValue = dataProvider.getMADValueInUSD(); uint256 madAmount = (amountInUsd * (10 ** 8)) / madValue; - bool success = usdt.transferFrom(msg.sender, address(this), amountInUsd * (10 ** 6)); + bool success = usdt.transferFrom( + msg.sender, + address(this), + amountInUsd * (10 ** 6) + ); if (!success) revert Vault__TransferFailed(); emit CollateralDeposited(msg.sender, amountInUsd * (10 ** 6)); @@ -54,7 +60,9 @@ contract Vault { @return bool True if the collateral was redeemed successfully */ - function redeemCollateral(uint256 amountInUsd) public payable returns (bool) { + function redeemCollateral( + uint256 amountInUsd + ) public payable returns (bool) { uint256 madValue = dataProvider.getMADValueInUSD(); uint256 madAmount = (amountInUsd * (10 ** 8)) / madValue; @@ -74,4 +82,9 @@ contract Vault { emit CollateralRedeemed(msg.sender, amountInUsd); return success; } + + function rebase() public { + uint256 madValue = dataProvider.getMADValueInUSD(); + madt.rebase(madValue); + } } From 5ad25b4fd0139d58dd10ef3e6417b495a19bd19d Mon Sep 17 00:00:00 2001 From: Imad Archid Date: Thu, 20 Feb 2025 12:12:11 +0100 Subject: [PATCH 6/6] style: formatting --- src/MADT.sol | 11 ++--------- src/Vault.sol | 14 +++----------- 2 files changed, 5 insertions(+), 20 deletions(-) diff --git a/src/MADT.sol b/src/MADT.sol index 21e0c77..16f465c 100644 --- a/src/MADT.sol +++ b/src/MADT.sol @@ -36,19 +36,12 @@ contract MADT is ERC20, Ownable { return (super.balanceOf(account) * rebaseFactor) / 1e18; } - function transfer( - address to, - uint256 amount - ) public override returns (bool) { + function transfer(address to, uint256 amount) public override returns (bool) { uint256 scaledAmount = (amount * 1e18) / rebaseFactor; return super.transfer(to, scaledAmount); } - function transferFrom( - address from, - address to, - uint256 amount - ) public override returns (bool) { + function transferFrom(address from, address to, uint256 amount) public override returns (bool) { uint256 scaledAmount = (amount * 1e18) / rebaseFactor; return super.transferFrom(from, to, scaledAmount); } diff --git a/src/Vault.sol b/src/Vault.sol index a99a1a6..48f6088 100644 --- a/src/Vault.sol +++ b/src/Vault.sol @@ -35,16 +35,10 @@ contract Vault { @return bool True if the collateral was deposited successfully */ - function depositCollateral( - uint256 amountInUsd - ) public payable returns (bool) { + function depositCollateral(uint256 amountInUsd) public payable returns (bool) { uint256 madValue = dataProvider.getMADValueInUSD(); uint256 madAmount = (amountInUsd * (10 ** 8)) / madValue; - bool success = usdt.transferFrom( - msg.sender, - address(this), - amountInUsd * (10 ** 6) - ); + bool success = usdt.transferFrom(msg.sender, address(this), amountInUsd * (10 ** 6)); if (!success) revert Vault__TransferFailed(); emit CollateralDeposited(msg.sender, amountInUsd * (10 ** 6)); @@ -60,9 +54,7 @@ contract Vault { @return bool True if the collateral was redeemed successfully */ - function redeemCollateral( - uint256 amountInUsd - ) public payable returns (bool) { + function redeemCollateral(uint256 amountInUsd) public payable returns (bool) { uint256 madValue = dataProvider.getMADValueInUSD(); uint256 madAmount = (amountInUsd * (10 ** 8)) / madValue;