From 562e1f18b6f166f333406caad793e74c1da23caa Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 2 Aug 2023 18:25:19 +0400 Subject: [PATCH 01/48] feat: refactor deployment script --- scripts/Deploy.s.sol | 60 +++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index b06b578d..2670e6f7 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -9,8 +9,28 @@ import "../contracts/test/SimpleRegistry.sol"; import "../contracts/Portfolio.sol"; import "../contracts/PositionRenderer.sol"; +// This script allows you to deploy the Portfolio contract and its dependencies, +// you can learn how to use it in our documentation: +// https://docs.primitive.xyz/protocol/contracts/deployments#deploying-portfolio contract Deploy is Script { + function deployIfNecessary( + NuguFactory factory, + string memory name, + bytes memory creationCode, + bytes32 salt + ) private returns (address at) { + at = factory.getDeployed(salt); + + if (at.code.length == 0) { + factory.deploy(salt, creationCode, 0); + console.log("%s %s (deployed)", name, at); + } else { + console.log("%s %s (skipped)", name, at); + } + } + function run(address weth, address registry) external { + console.log("~"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -26,41 +46,51 @@ contract Deploy is Script { // If no WETH address is provided, we deploy a new contract if (weth == address(0)) { - weth = factory.deploy(keccak256("WETH"), type(WETH).creationCode, 0); + deployIfNecessary( + factory, "WETH", type(WETH).creationCode, keccak256("WETH") + ); + } else { + console.log("WETH %s (reused)", weth); } // Same thing for the Portfolio registry if (registry == address(0)) { - registry = factory.deploy( - keccak256("SimpleRegistry"), + deployIfNecessary( + factory, + "Registry", type(SimpleRegistry).creationCode, - 0 + keccak256(type(SimpleRegistry).creationCode) ); + } else { + console.log("Registry %s (reused)", registry); } // First we deploy the PositionRenderer contract - address positionRenderer = factory.deploy( - keccak256("PositionRenderer"), + address positionRenderer = deployIfNecessary( + factory, + "PositionRenderer", type(PositionRenderer).creationCode, - 0 + keccak256(type(PositionRenderer).creationCode) ); // Then we can deploy the Portfolio contract - address portfolio = factory.deploy( - keccak256("Portfolio"), + address portfolio = deployIfNecessary( + factory, + "Portfolio", abi.encodePacked( type(Portfolio).creationCode, abi.encode(weth, registry, positionRenderer) ), - 0 + keccak256(type(Portfolio).creationCode) ); - console.log(unicode"🚀 Contracts deployed!"); - console.log("WETH:", weth); - console.log("Registry:", registry); - console.log("PositionRenderer:", positionRenderer); - console.log("Portfolio:", portfolio); + console.log( + "NormalStrategy", + Portfolio(payable(portfolio)).DEFAULT_STRATEGY(), + "(deployed)" + ); vm.stopBroadcast(); + console.log("~"); } } From 13a3f7e2a488763d797a05f0b1cd2edf364a4ad3 Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 3 Aug 2023 13:34:26 +0400 Subject: [PATCH 02/48] feat: print JSON on deployment --- scripts/Deploy.s.sol | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 2670e6f7..70660ad3 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -13,6 +13,22 @@ import "../contracts/PositionRenderer.sol"; // you can learn how to use it in our documentation: // https://docs.primitive.xyz/protocol/contracts/deployments#deploying-portfolio contract Deploy is Script { + function printJSON( + string memory name, + address at, + string memory status, + bool isLast + ) private view { + console.log( + string.concat( + '"%s":{"address":"%s","status":"%s"}', isLast ? "" : "," + ), + name, + at, + status + ); + } + function deployIfNecessary( NuguFactory factory, string memory name, @@ -23,14 +39,14 @@ contract Deploy is Script { if (at.code.length == 0) { factory.deploy(salt, creationCode, 0); - console.log("%s %s (deployed)", name, at); + printJSON(name, at, "deployed", false); } else { - console.log("%s %s (skipped)", name, at); + printJSON(name, at, "skipped", false); } } function run(address weth, address registry) external { - console.log("~"); + console.log("{"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -50,7 +66,7 @@ contract Deploy is Script { factory, "WETH", type(WETH).creationCode, keccak256("WETH") ); } else { - console.log("WETH %s (reused)", weth); + printJSON("WETH", weth, "reused", false); } // Same thing for the Portfolio registry @@ -62,7 +78,7 @@ contract Deploy is Script { keccak256(type(SimpleRegistry).creationCode) ); } else { - console.log("Registry %s (reused)", registry); + printJSON("Registry", registry, "reused", false); } // First we deploy the PositionRenderer contract @@ -84,13 +100,14 @@ contract Deploy is Script { keccak256(type(Portfolio).creationCode) ); - console.log( + printJSON( "NormalStrategy", Portfolio(payable(portfolio)).DEFAULT_STRATEGY(), - "(deployed)" + "deployed", + true ); vm.stopBroadcast(); - console.log("~"); + console.log("}"); } } From 05fd6077b6b35c4ac7bf9938a31234f36967730f Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 3 Aug 2023 14:02:55 +0400 Subject: [PATCH 03/48] feat: add special character to logs --- scripts/Deploy.s.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 70660ad3..57349c5f 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -46,6 +46,7 @@ contract Deploy is Script { } function run(address weth, address registry) external { + console.log("~"); console.log("{"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -109,5 +110,6 @@ contract Deploy is Script { vm.stopBroadcast(); console.log("}"); + console.log("~"); } } From 876c30b1a461cfcc0aef3822ccec7b0e5afaadcb Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 11 Aug 2023 11:14:52 +0400 Subject: [PATCH 04/48] feat: small changes to the deployment script --- scripts/Deploy.s.sol | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 57349c5f..c7e3bfb9 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -30,41 +30,42 @@ contract Deploy is Script { } function deployIfNecessary( - NuguFactory factory, + NuguFactory nugu, string memory name, bytes memory creationCode, bytes32 salt ) private returns (address at) { - at = factory.getDeployed(salt); + at = nugu.getDeployed(salt); if (at.code.length == 0) { - factory.deploy(salt, creationCode, 0); + nugu.deploy(salt, creationCode, 0); printJSON(name, at, "deployed", false); } else { printJSON(name, at, "skipped", false); } } - function run(address weth, address registry) external { + function run(address factory, address weth, address registry) external { console.log("~"); console.log("{"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); // Here we specify the factory we want to use - NuguFactory factory = - NuguFactory(0x0000A79C3D8124ED3a7F8EC8427E4DC43a2B154d); + NuguFactory nugu = NuguFactory(create3Factory); - // Let's check if the contract is deployed on this network + // Let's check if the factory contract is deployed on this network, if + // it's not, it's better to avoid deploying it from here, as the address + // is canonical across all networks, we need a specific script for that. require( - address(factory).code.length > 0, + address(nugu).code.length > 0, "Nugu Factory not deployed on this network!" ); // If no WETH address is provided, we deploy a new contract if (weth == address(0)) { deployIfNecessary( - factory, "WETH", type(WETH).creationCode, keccak256("WETH") + nugu, "WETH", type(WETH).creationCode, keccak256("WETH") ); } else { printJSON("WETH", weth, "reused", false); @@ -73,7 +74,7 @@ contract Deploy is Script { // Same thing for the Portfolio registry if (registry == address(0)) { deployIfNecessary( - factory, + nugu, "Registry", type(SimpleRegistry).creationCode, keccak256(type(SimpleRegistry).creationCode) @@ -84,7 +85,7 @@ contract Deploy is Script { // First we deploy the PositionRenderer contract address positionRenderer = deployIfNecessary( - factory, + nugu, "PositionRenderer", type(PositionRenderer).creationCode, keccak256(type(PositionRenderer).creationCode) @@ -92,7 +93,7 @@ contract Deploy is Script { // Then we can deploy the Portfolio contract address portfolio = deployIfNecessary( - factory, + nugu, "Portfolio", abi.encodePacked( type(Portfolio).creationCode, From 8e0e2fff6bf450de063a1e3308143b5265c9be5a Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 11 Aug 2023 12:01:47 +0400 Subject: [PATCH 05/48] feat: fix var name in deployment script --- scripts/Deploy.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index c7e3bfb9..4f2abaaf 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -52,7 +52,7 @@ contract Deploy is Script { vm.startBroadcast(deployerPrivateKey); // Here we specify the factory we want to use - NuguFactory nugu = NuguFactory(create3Factory); + NuguFactory nugu = NuguFactory(factory); // Let's check if the factory contract is deployed on this network, if // it's not, it's better to avoid deploying it from here, as the address From 1622bb2fc94115abc6eee93c3ffd16f66e889d2a Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 16 Aug 2023 17:23:01 +0400 Subject: [PATCH 06/48] build: update nugu factory --- lib/nugu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nugu b/lib/nugu index a154d73d..71fbe1e7 160000 --- a/lib/nugu +++ b/lib/nugu @@ -1 +1 @@ -Subproject commit a154d73d07cd0a099d5c9e78f9f91c9c2e505fe2 +Subproject commit 71fbe1e7e1441147886e70425cbb4f6b645ee286 From ee85f45abf3235289dff8d9d47621880f224bc8a Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 16 Aug 2023 17:23:23 +0400 Subject: [PATCH 07/48] chore: update Deploy script --- scripts/Deploy.s.sol | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 4f2abaaf..27f33dd7 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -31,11 +31,12 @@ contract Deploy is Script { function deployIfNecessary( NuguFactory nugu, + address deployer, string memory name, bytes memory creationCode, bytes32 salt ) private returns (address at) { - at = nugu.getDeployed(salt); + at = nugu.getDeployed(deployer, salt); if (at.code.length == 0) { nugu.deploy(salt, creationCode, 0); @@ -46,7 +47,6 @@ contract Deploy is Script { } function run(address factory, address weth, address registry) external { - console.log("~"); console.log("{"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -65,7 +65,11 @@ contract Deploy is Script { // If no WETH address is provided, we deploy a new contract if (weth == address(0)) { deployIfNecessary( - nugu, "WETH", type(WETH).creationCode, keccak256("WETH") + nugu, + vm.addr(deployerPrivateKey), + "WETH", + type(WETH).creationCode, + keccak256("WETH") ); } else { printJSON("WETH", weth, "reused", false); @@ -75,6 +79,7 @@ contract Deploy is Script { if (registry == address(0)) { deployIfNecessary( nugu, + vm.addr(deployerPrivateKey), "Registry", type(SimpleRegistry).creationCode, keccak256(type(SimpleRegistry).creationCode) @@ -86,6 +91,7 @@ contract Deploy is Script { // First we deploy the PositionRenderer contract address positionRenderer = deployIfNecessary( nugu, + vm.addr(deployerPrivateKey), "PositionRenderer", type(PositionRenderer).creationCode, keccak256(type(PositionRenderer).creationCode) @@ -94,12 +100,13 @@ contract Deploy is Script { // Then we can deploy the Portfolio contract address portfolio = deployIfNecessary( nugu, + vm.addr(deployerPrivateKey), "Portfolio", abi.encodePacked( type(Portfolio).creationCode, abi.encode(weth, registry, positionRenderer) ), - keccak256(type(Portfolio).creationCode) + 0x5EE6BA1393528E858144FF71FBB117AFA1AE672118B68E777631F459DE307EB8 ); printJSON( @@ -111,6 +118,5 @@ contract Deploy is Script { vm.stopBroadcast(); console.log("}"); - console.log("~"); } } From a687a1dbd9814a57b4deaaf2901d469dcd241902 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 2 Aug 2023 18:25:19 +0400 Subject: [PATCH 08/48] feat: refactor deployment script --- scripts/Deploy.s.sol | 60 +++++++++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index b06b578d..2670e6f7 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -9,8 +9,28 @@ import "../contracts/test/SimpleRegistry.sol"; import "../contracts/Portfolio.sol"; import "../contracts/PositionRenderer.sol"; +// This script allows you to deploy the Portfolio contract and its dependencies, +// you can learn how to use it in our documentation: +// https://docs.primitive.xyz/protocol/contracts/deployments#deploying-portfolio contract Deploy is Script { + function deployIfNecessary( + NuguFactory factory, + string memory name, + bytes memory creationCode, + bytes32 salt + ) private returns (address at) { + at = factory.getDeployed(salt); + + if (at.code.length == 0) { + factory.deploy(salt, creationCode, 0); + console.log("%s %s (deployed)", name, at); + } else { + console.log("%s %s (skipped)", name, at); + } + } + function run(address weth, address registry) external { + console.log("~"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -26,41 +46,51 @@ contract Deploy is Script { // If no WETH address is provided, we deploy a new contract if (weth == address(0)) { - weth = factory.deploy(keccak256("WETH"), type(WETH).creationCode, 0); + deployIfNecessary( + factory, "WETH", type(WETH).creationCode, keccak256("WETH") + ); + } else { + console.log("WETH %s (reused)", weth); } // Same thing for the Portfolio registry if (registry == address(0)) { - registry = factory.deploy( - keccak256("SimpleRegistry"), + deployIfNecessary( + factory, + "Registry", type(SimpleRegistry).creationCode, - 0 + keccak256(type(SimpleRegistry).creationCode) ); + } else { + console.log("Registry %s (reused)", registry); } // First we deploy the PositionRenderer contract - address positionRenderer = factory.deploy( - keccak256("PositionRenderer"), + address positionRenderer = deployIfNecessary( + factory, + "PositionRenderer", type(PositionRenderer).creationCode, - 0 + keccak256(type(PositionRenderer).creationCode) ); // Then we can deploy the Portfolio contract - address portfolio = factory.deploy( - keccak256("Portfolio"), + address portfolio = deployIfNecessary( + factory, + "Portfolio", abi.encodePacked( type(Portfolio).creationCode, abi.encode(weth, registry, positionRenderer) ), - 0 + keccak256(type(Portfolio).creationCode) ); - console.log(unicode"🚀 Contracts deployed!"); - console.log("WETH:", weth); - console.log("Registry:", registry); - console.log("PositionRenderer:", positionRenderer); - console.log("Portfolio:", portfolio); + console.log( + "NormalStrategy", + Portfolio(payable(portfolio)).DEFAULT_STRATEGY(), + "(deployed)" + ); vm.stopBroadcast(); + console.log("~"); } } From 1d92b63cae162db12985e02e3466e385412d2b42 Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 3 Aug 2023 13:34:26 +0400 Subject: [PATCH 09/48] feat: print JSON on deployment --- scripts/Deploy.s.sol | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 2670e6f7..70660ad3 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -13,6 +13,22 @@ import "../contracts/PositionRenderer.sol"; // you can learn how to use it in our documentation: // https://docs.primitive.xyz/protocol/contracts/deployments#deploying-portfolio contract Deploy is Script { + function printJSON( + string memory name, + address at, + string memory status, + bool isLast + ) private view { + console.log( + string.concat( + '"%s":{"address":"%s","status":"%s"}', isLast ? "" : "," + ), + name, + at, + status + ); + } + function deployIfNecessary( NuguFactory factory, string memory name, @@ -23,14 +39,14 @@ contract Deploy is Script { if (at.code.length == 0) { factory.deploy(salt, creationCode, 0); - console.log("%s %s (deployed)", name, at); + printJSON(name, at, "deployed", false); } else { - console.log("%s %s (skipped)", name, at); + printJSON(name, at, "skipped", false); } } function run(address weth, address registry) external { - console.log("~"); + console.log("{"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -50,7 +66,7 @@ contract Deploy is Script { factory, "WETH", type(WETH).creationCode, keccak256("WETH") ); } else { - console.log("WETH %s (reused)", weth); + printJSON("WETH", weth, "reused", false); } // Same thing for the Portfolio registry @@ -62,7 +78,7 @@ contract Deploy is Script { keccak256(type(SimpleRegistry).creationCode) ); } else { - console.log("Registry %s (reused)", registry); + printJSON("Registry", registry, "reused", false); } // First we deploy the PositionRenderer contract @@ -84,13 +100,14 @@ contract Deploy is Script { keccak256(type(Portfolio).creationCode) ); - console.log( + printJSON( "NormalStrategy", Portfolio(payable(portfolio)).DEFAULT_STRATEGY(), - "(deployed)" + "deployed", + true ); vm.stopBroadcast(); - console.log("~"); + console.log("}"); } } From 67370584069643876ef368cbb5a8daab36b04333 Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 3 Aug 2023 14:02:55 +0400 Subject: [PATCH 10/48] feat: add special character to logs --- scripts/Deploy.s.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 70660ad3..57349c5f 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -46,6 +46,7 @@ contract Deploy is Script { } function run(address weth, address registry) external { + console.log("~"); console.log("{"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -109,5 +110,6 @@ contract Deploy is Script { vm.stopBroadcast(); console.log("}"); + console.log("~"); } } From 8ef73fa8da14578ef4b4cc47b940732903ccd05d Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 11 Aug 2023 11:14:52 +0400 Subject: [PATCH 11/48] feat: small changes to the deployment script --- scripts/Deploy.s.sol | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 57349c5f..c7e3bfb9 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -30,41 +30,42 @@ contract Deploy is Script { } function deployIfNecessary( - NuguFactory factory, + NuguFactory nugu, string memory name, bytes memory creationCode, bytes32 salt ) private returns (address at) { - at = factory.getDeployed(salt); + at = nugu.getDeployed(salt); if (at.code.length == 0) { - factory.deploy(salt, creationCode, 0); + nugu.deploy(salt, creationCode, 0); printJSON(name, at, "deployed", false); } else { printJSON(name, at, "skipped", false); } } - function run(address weth, address registry) external { + function run(address factory, address weth, address registry) external { console.log("~"); console.log("{"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); // Here we specify the factory we want to use - NuguFactory factory = - NuguFactory(0x0000A79C3D8124ED3a7F8EC8427E4DC43a2B154d); + NuguFactory nugu = NuguFactory(create3Factory); - // Let's check if the contract is deployed on this network + // Let's check if the factory contract is deployed on this network, if + // it's not, it's better to avoid deploying it from here, as the address + // is canonical across all networks, we need a specific script for that. require( - address(factory).code.length > 0, + address(nugu).code.length > 0, "Nugu Factory not deployed on this network!" ); // If no WETH address is provided, we deploy a new contract if (weth == address(0)) { deployIfNecessary( - factory, "WETH", type(WETH).creationCode, keccak256("WETH") + nugu, "WETH", type(WETH).creationCode, keccak256("WETH") ); } else { printJSON("WETH", weth, "reused", false); @@ -73,7 +74,7 @@ contract Deploy is Script { // Same thing for the Portfolio registry if (registry == address(0)) { deployIfNecessary( - factory, + nugu, "Registry", type(SimpleRegistry).creationCode, keccak256(type(SimpleRegistry).creationCode) @@ -84,7 +85,7 @@ contract Deploy is Script { // First we deploy the PositionRenderer contract address positionRenderer = deployIfNecessary( - factory, + nugu, "PositionRenderer", type(PositionRenderer).creationCode, keccak256(type(PositionRenderer).creationCode) @@ -92,7 +93,7 @@ contract Deploy is Script { // Then we can deploy the Portfolio contract address portfolio = deployIfNecessary( - factory, + nugu, "Portfolio", abi.encodePacked( type(Portfolio).creationCode, From 13f5ca65273f2cc5a8cdb17fcc9be9100b8af562 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 11 Aug 2023 12:01:47 +0400 Subject: [PATCH 12/48] feat: fix var name in deployment script --- scripts/Deploy.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index c7e3bfb9..4f2abaaf 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -52,7 +52,7 @@ contract Deploy is Script { vm.startBroadcast(deployerPrivateKey); // Here we specify the factory we want to use - NuguFactory nugu = NuguFactory(create3Factory); + NuguFactory nugu = NuguFactory(factory); // Let's check if the factory contract is deployed on this network, if // it's not, it's better to avoid deploying it from here, as the address From 084073f3900d4049e5a5dccd345065da26ffd751 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 16 Aug 2023 17:23:01 +0400 Subject: [PATCH 13/48] build: update nugu factory --- lib/nugu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/nugu b/lib/nugu index a154d73d..71fbe1e7 160000 --- a/lib/nugu +++ b/lib/nugu @@ -1 +1 @@ -Subproject commit a154d73d07cd0a099d5c9e78f9f91c9c2e505fe2 +Subproject commit 71fbe1e7e1441147886e70425cbb4f6b645ee286 From b8b5dcdd33bd15f78c22f66b14e8183b7c49a396 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 16 Aug 2023 17:23:23 +0400 Subject: [PATCH 14/48] chore: update Deploy script --- scripts/Deploy.s.sol | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 4f2abaaf..27f33dd7 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -31,11 +31,12 @@ contract Deploy is Script { function deployIfNecessary( NuguFactory nugu, + address deployer, string memory name, bytes memory creationCode, bytes32 salt ) private returns (address at) { - at = nugu.getDeployed(salt); + at = nugu.getDeployed(deployer, salt); if (at.code.length == 0) { nugu.deploy(salt, creationCode, 0); @@ -46,7 +47,6 @@ contract Deploy is Script { } function run(address factory, address weth, address registry) external { - console.log("~"); console.log("{"); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -65,7 +65,11 @@ contract Deploy is Script { // If no WETH address is provided, we deploy a new contract if (weth == address(0)) { deployIfNecessary( - nugu, "WETH", type(WETH).creationCode, keccak256("WETH") + nugu, + vm.addr(deployerPrivateKey), + "WETH", + type(WETH).creationCode, + keccak256("WETH") ); } else { printJSON("WETH", weth, "reused", false); @@ -75,6 +79,7 @@ contract Deploy is Script { if (registry == address(0)) { deployIfNecessary( nugu, + vm.addr(deployerPrivateKey), "Registry", type(SimpleRegistry).creationCode, keccak256(type(SimpleRegistry).creationCode) @@ -86,6 +91,7 @@ contract Deploy is Script { // First we deploy the PositionRenderer contract address positionRenderer = deployIfNecessary( nugu, + vm.addr(deployerPrivateKey), "PositionRenderer", type(PositionRenderer).creationCode, keccak256(type(PositionRenderer).creationCode) @@ -94,12 +100,13 @@ contract Deploy is Script { // Then we can deploy the Portfolio contract address portfolio = deployIfNecessary( nugu, + vm.addr(deployerPrivateKey), "Portfolio", abi.encodePacked( type(Portfolio).creationCode, abi.encode(weth, registry, positionRenderer) ), - keccak256(type(Portfolio).creationCode) + 0x5EE6BA1393528E858144FF71FBB117AFA1AE672118B68E777631F459DE307EB8 ); printJSON( @@ -111,6 +118,5 @@ contract Deploy is Script { vm.stopBroadcast(); console.log("}"); - console.log("~"); } } From 41c7ac9473f31843e450493968b1dcad1674d149 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 16 Aug 2023 17:58:28 +0400 Subject: [PATCH 15/48] feat: update deployment salt for Portfolio --- scripts/Deploy.s.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/Deploy.s.sol b/scripts/Deploy.s.sol index 27f33dd7..63abc30e 100644 --- a/scripts/Deploy.s.sol +++ b/scripts/Deploy.s.sol @@ -106,7 +106,7 @@ contract Deploy is Script { type(Portfolio).creationCode, abi.encode(weth, registry, positionRenderer) ), - 0x5EE6BA1393528E858144FF71FBB117AFA1AE672118B68E777631F459DE307EB8 + 0x8B9A3E646B237114DA81624C540FBA61A7E7FFFEA48B90454E66F547AA5B706C ); printJSON( From 10bad182db5475f924238cc5b721dba95eec1613 Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 17 Aug 2023 12:01:01 +0400 Subject: [PATCH 16/48] chore: add prepare data script --- scripts/Prepare.s.sol | 104 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 scripts/Prepare.s.sol diff --git a/scripts/Prepare.s.sol b/scripts/Prepare.s.sol new file mode 100644 index 00000000..5105e1d6 --- /dev/null +++ b/scripts/Prepare.s.sol @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "solmate/tokens/WETH.sol"; +import "nugu/NuguFactory.sol"; + +import "../contracts/test/SimpleRegistry.sol"; +import "../contracts/Portfolio.sol"; +import "../contracts/PositionRenderer.sol"; + +// This script prepares all the transaction data that can be used to deploy +// Portfolio and its dependency contracts. +// +// Here are the steps to use it: +// 1. Be sure to update all the constants below: contract addresses and salts. +// 2. Run the script with: +// `forge script ./scripts/Prepare.s.sol --via-ir --optimizer-runs 0`. +// 3. Visit https://app.safe.global/, click on your multisig wallet, then on +// "New transaction" and finally "Transaction builder". +// 4. Paste the address of the Nugu factory and enable the "custom data". +// 5. Then for each data generated by this script (3 in total), you'll need to +// copy and paste it into the "data" field of the transaction builder and click +// the "Add transaction" button. +// 6. After that, a total of 3 transactions must compose the batch. You can then +// hit "Create Batch", and either "Simulate" or directly "Send Batch". +// 7. Last step is simply to follow the instructions to send the transaction. + +address constant SAFE_ADDRESS = 0x8cDb0095ceddb092aD592aCE2971e4f364b5E8eE; +address constant NUGU_ADDRESS = 0xe95671B131FD0f401BA6a68F55A79542E27C43De; +address constant WETH_ADDRESS = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6; + +bytes32 constant REGISTRY_SALT = keccak256("REGISTRY0"); +bytes32 constant POSITION_RENDERER_SALT = keccak256("POSITION_RENDERER0"); +bytes32 constant PORTFOLIO_SALT = keccak256("PORTFOLIO0"); + +contract Deploy is Script { + bytes internal constant PROXY_BYTECODE = + hex"67363d3d37363d34f03d5260086018f3"; + bytes32 internal constant PROXY_BYTECODE_HASH = keccak256(PROXY_BYTECODE); + + function run() external { + bytes memory registryDeployData = abi.encodeCall( + NuguFactory.deploy, + (REGISTRY_SALT, type(SimpleRegistry).creationCode, 0) + ); + + bytes memory positionRendererDeployData = abi.encodeCall( + NuguFactory.deploy, + (POSITION_RENDERER_SALT, type(PositionRenderer).creationCode, 0) + ); + + address predictedRegistryAddress = + getDeployed(NUGU_ADDRESS, SAFE_ADDRESS, REGISTRY_SALT); + + address predictedPositionRendererAddress = + getDeployed(NUGU_ADDRESS, SAFE_ADDRESS, POSITION_RENDERER_SALT); + + bytes memory portfolioDeployData = abi.encodeCall( + NuguFactory.deploy, + ( + PORTFOLIO_SALT, + abi.encodePacked( + type(Portfolio).creationCode, + abi.encode( + WETH_ADDRESS, + predictedRegistryAddress, + predictedPositionRendererAddress + ) + ), + 0 + ) + ); + + console.log("\nRegistry deploy data:"); + console.logBytes(registryDeployData); + + console.log("\nPositionRenderer deploy data:"); + console.logBytes(positionRendererDeployData); + + console.log("\nPortfolio deploy data:"); + console.logBytes(portfolioDeployData); + } + + function getDeployed( + address factory, + address deployer, + bytes32 salt + ) internal view returns (address) { + salt = keccak256(abi.encodePacked(deployer, salt)); + + address proxy = Bytes32AddressLib.fromLast20Bytes( + keccak256( + abi.encodePacked( + bytes1(0xFF), factory, salt, PROXY_BYTECODE_HASH + ) + ) + ); + + return Bytes32AddressLib.fromLast20Bytes( + keccak256(abi.encodePacked(hex"d694", proxy, hex"01")) + ); + } +} From ed591f75df3e0827490e5eab54fbc94eef933579 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 18 Aug 2023 12:13:55 +0400 Subject: [PATCH 17/48] feat: update NFT visual --- contracts/PositionRenderer.sol | 610 +++++++++++++++++++++++++++++---- 1 file changed, 543 insertions(+), 67 deletions(-) diff --git a/contracts/PositionRenderer.sol b/contracts/PositionRenderer.sol index 9a9782b5..9157bd9e 100644 --- a/contracts/PositionRenderer.sol +++ b/contracts/PositionRenderer.sol @@ -14,24 +14,66 @@ contract PositionRenderer { using Strings for *; using SafeCastLib for *; + struct Pair { + address asset; + string assetSymbol; + string assetName; + uint8 assetDecimals; + address quote; + string quoteSymbol; + string quoteName; + uint8 quoteDecimals; + } + + struct Pool { + uint128 virtualX; + uint128 virtualY; + uint16 feeBasisPoints; + uint16 priorityFeeBasisPoints; + address controller; + address strategy; + uint256 spotPriceWad; + } + + struct Config { + uint128 strikePriceWad; + uint32 volatilityBasisPoints; + uint32 durationSeconds; + uint32 creationTimestamp; + bool isPerpetual; + } + + struct Properties { + Pair pair; + Pool pool; + Config config; + } + + string private constant PRIMITIVE_LOGO = + ''; + function uri(uint256 id) external view returns (string memory) { + Properties memory properties = _getProperties(id); + return string.concat( "data:application/json;base64,", Base64.encode( bytes( abi.encodePacked( '{"name":"', - _generateName(id), + _generateName(properties), '","image":"', - _generateImage(), + _generateImage(properties), '","license":"MIT","creator":"primitive.eth",', - '"description":"Concentrated liquidity tokens of a two-token AMM",', + '"description":"This NFT represents a liquidity position in a Portfolio pool. The owner of this NFT can modify or redeem this position.\\n\\n', + unicode"⚠️ WARNING: Transferring this NFT makes the new recipient the owner of the position.", + '",', '"properties":{', - _generatePair(id), + _generatePair(properties), ",", - _generatePool(id), + _generatePool(properties), ",", - _generateConfig(id), + _generateConfig(properties), "}}" ) ) @@ -39,113 +81,547 @@ contract PositionRenderer { ); } - function _generateName(uint256 id) private view returns (string memory) { - (address tokenAsset,, address tokenQuote,) = IPortfolio(msg.sender) - .pairs(PoolId.wrap(id.safeCastTo64()).pairId()); - + function _generateImage(Properties memory properties) + private + view + returns (string memory) + { return string.concat( - "Primitive Portfolio LP ", - ERC20(tokenAsset).symbol(), - "-", - ERC20(tokenQuote).symbol() + "data:image/svg+xml;base64,", + Base64.encode(bytes(_generateSVG(properties))) ); } - function _generateImage() private pure returns (string memory) { + function _getPair(uint256 id) internal view returns (Pair memory) { + ( + address tokenAsset, + uint8 decimalsAsset, + address tokenQuote, + uint8 decimalsQuote + ) = IPortfolio(msg.sender).pairs(uint24(id)); + + return Pair({ + asset: tokenAsset, + assetSymbol: ERC20(tokenAsset).symbol(), + assetName: ERC20(tokenAsset).name(), + assetDecimals: decimalsAsset, + quote: tokenQuote, + quoteSymbol: ERC20(tokenQuote).symbol(), + quoteName: ERC20(tokenQuote).name(), + quoteDecimals: decimalsQuote + }); + } + + function _getPool(uint256 id) internal view returns (Pool memory) { + ( + uint128 virtualX, + uint128 virtualY, + , + , + uint16 feeBasisPoints, + uint16 priorityFeeBasisPoints, + address controller, + address strategy + ) = IPortfolio(msg.sender).pools(uint64(id)); + + uint256 spotPriceWad = IPortfolio(msg.sender).getSpotPrice(uint64(id)); + + return Pool({ + virtualX: virtualX, + virtualY: virtualY, + feeBasisPoints: feeBasisPoints, + priorityFeeBasisPoints: priorityFeeBasisPoints, + controller: controller, + strategy: strategy, + spotPriceWad: spotPriceWad + }); + } + + function _getConfig( + uint256 id, + address strategy + ) internal view returns (Config memory) { + ( + uint128 strikePriceWad, + uint32 volatilityBasisPoints, + uint32 durationSeconds, + uint32 creationTimestamp, + bool isPerpetual + ) = NormalStrategy(strategy).configs(uint64(id)); + + return Config({ + strikePriceWad: strikePriceWad, + volatilityBasisPoints: volatilityBasisPoints, + durationSeconds: durationSeconds, + creationTimestamp: creationTimestamp, + isPerpetual: isPerpetual + }); + } + + function _getProperties(uint256 id) + private + view + returns (Properties memory) + { + Pair memory pair = _getPair(id); + Pool memory pool = _getPool(id); + Config memory config = _getConfig(id, pool.strategy); + + return Properties({ pair: pair, pool: pool, config: config }); + } + + function _generateName(Properties memory properties) + private + pure + returns (string memory) + { return string.concat( - "data:image/svg+xml;base64,", - Base64.encode( - bytes( - '' - ) - ) + "Primitive Portfolio LP ", + properties.pair.assetSymbol, + "-", + properties.pair.quoteSymbol ); } - function _generatePair(uint256 id) private view returns (string memory) { - (address tokenAsset,, address tokenQuote,) = IPortfolio(msg.sender) - .pairs(PoolId.wrap(id.safeCastTo64()).pairId()); - + function _generatePair(Properties memory properties) + private + pure + returns (string memory) + { return string.concat( '"asset_name":"', - ERC20(tokenAsset).name(), + properties.pair.assetName, '",', '"asset_symbol":"', - ERC20(tokenAsset).symbol(), + properties.pair.assetSymbol, '",', '"asset_address":"', - Strings.toHexString(tokenAsset), + properties.pair.asset.toHexString(), '",', '"quote_name":"', - ERC20(tokenQuote).name(), + properties.pair.quoteName, '",', '"quote_symbol":"', - ERC20(tokenQuote).symbol(), + properties.pair.quoteSymbol, '",', '"quote_address":"', - Strings.toHexString(tokenQuote), + properties.pair.quote.toHexString(), '"' ); } - function _generatePool(uint256 id) private view returns (string memory) { - ( - , - , - , - , - uint16 feeBasisPoints, - uint16 priorityFeeBasisPoints, - address controller, - address strategy - ) = IPortfolio(msg.sender).pools(uint64(id)); - + function _generatePool(Properties memory properties) + private + pure + returns (string memory) + { return string.concat( '"fee_basis_points":"', - feeBasisPoints.toString(), + properties.pool.feeBasisPoints.toString(), '",', '"priority_fee_basis_points":"', - priorityFeeBasisPoints.toString(), + properties.pool.priorityFeeBasisPoints.toString(), '",', '"controller":"', - Strings.toHexString(controller), + Strings.toHexString(properties.pool.controller), '",', '"strategy":"', - Strings.toHexString(strategy), + Strings.toHexString(properties.pool.strategy), '"' ); } - function _generateConfig(uint256 id) private view returns (string memory) { - (,,,,,,, address strategy) = IPortfolio(msg.sender).pools(uint64(id)); - - if (strategy == address(0)) { - strategy = IPortfolio(msg.sender).DEFAULT_STRATEGY(); - } - - ( - uint128 strikePriceWad, - uint32 volatilityBasisPoints, - uint32 durationSeconds, - uint32 creationTimestamp, - bool isPerpetual - ) = NormalStrategy(strategy).configs(uint64(id)); - + function _generateConfig(Properties memory properties) + private + pure + returns (string memory) + { return string.concat( '"strike_price_wad":"', - strikePriceWad.toString(), + properties.config.strikePriceWad.toString(), '",', '"volatility_basis_points":"', - volatilityBasisPoints.toString(), + properties.config.volatilityBasisPoints.toString(), '",', '"duration_seconds":"', - durationSeconds.toString(), + properties.config.durationSeconds.toString(), '",', '"creation_timestamp":"', - creationTimestamp.toString(), + properties.config.creationTimestamp.toString(), '",', '"is_perpetual":', - isPerpetual ? "true" : "false" + properties.config.isPerpetual ? "true" : "false" ); } + + function _generateSVG(Properties memory properties) + private + view + returns (string memory) + { + return string.concat( + '', + _generateSVGNoise(), + _generateSVGGradient(), + '' + '', + PRIMITIVE_LOGO, + _generateStats(properties), + "" + ); + } + + function _generateStats(Properties memory properties) + private + view + returns (string memory) + { + return string.concat( + _generateSVGTitle(properties), + _generateSVGSpotPrice(properties), + _generateSVGStrikePrice(properties), + _generateSVGReserves(properties), + _generateSVGPoolValuation(properties), + _generateSVGSwapFee(properties) + ); + } + + function _generateSVGNoise() internal pure returns (string memory) { + return + ' '; + } + + function _generateSVGGradient() internal pure returns (string memory) { + return string.concat( + '' + ); + } + + function _generateSVGTitle(Properties memory properties) + internal + view + returns (string memory) + { + return string.concat( + _drawText( + 550, + 75, + "#fff", + "3.25em", + "monospace", + "end", + string.concat( + properties.pair.assetSymbol, + " - ", + properties.pair.quoteSymbol + ) + ), + _drawText( + 550, + 100, + "#ffffff80", + "1.75em", + "monospace", + "end", + properties.config.isPerpetual + ? "Never expires" + : _calculateCountdown( + properties.config.creationTimestamp + + properties.config.durationSeconds + ) + ) + ); + } + + function _generateSVGSpotPrice(Properties memory properties) + internal + pure + returns (string memory) + { + return string.concat( + _drawText( + 50, + 200, + "#ffffff80", + "1.75em", + "monospace", + "start", + "Spot Price" + ), + _drawText( + 50, + 240, + "#fff", + "2.5em", + "monospace", + "start", + string.concat( + properties.pool.spotPriceWad.toString(), + " ", + properties.pair.quoteSymbol + ) + ) + ); + } + + function _generateSVGStrikePrice(Properties memory properties) + internal + pure + returns (string memory) + { + return string.concat( + _drawText( + 325, + 200, + "#ffffff80", + "1.75em", + "monospace", + "start", + "Strike Price" + ), + _drawText( + 325, + 240, + "#fff", + "2.5em", + "monospace", + "start", + string.concat( + properties.config.strikePriceWad.toString(), + " ", + properties.pair.quoteSymbol + ) + ) + ); + } + + function _generateSVGReserves(Properties memory properties) + internal + pure + returns (string memory) + { + return ( + string.concat( + _drawText( + 50, + 320, + "#ffffff80", + "1.75em", + "monospace", + "start", + "Asset Reserve" + ), + _drawText( + 50, + 360, + "#fff", + "2.5em", + "monospace", + "start", + string.concat( + properties.pool.virtualX.toString(), + " ", + properties.pair.assetSymbol + ) + ), + _drawText( + 325, + 320, + "#ffffff80", + "1.75em", + "monospace", + "start", + "Quote Reserve" + ), + _drawText( + 325, + 360, + "#fff", + "2.5em", + "monospace", + "start", + string.concat( + properties.pool.virtualY.toString(), + " ", + properties.pair.quoteSymbol + ) + ) + ) + ); + } + + function _generateSVGPoolValuation(Properties memory properties) + internal + pure + returns (string memory) + { + return ( + string.concat( + _drawText( + 50, + 440, + "#ffffff80", + "1.75em", + "monospace", + "start", + "Pool Valuation" + ), + _drawText( + 50, + 480, + "#fff", + "2.5em", + "monospace", + "start", + string.concat( + properties.config.strikePriceWad.toString(), + " ", + properties.pair.quoteSymbol + ) + ) + ) + ); + } + + function _generateSVGSwapFee(Properties memory properties) + internal + pure + returns (string memory) + { + return ( + string.concat( + _drawText( + 325, + 440, + "#ffffff80", + "1.75em", + "monospace", + "start", + "Swap Fee" + ), + _drawText( + 325, + 480, + "#fff", + "2.5em", + "monospace", + "start", + string.concat( + properties.pool.feeBasisPoints.toString(), " %" + ) + ) + ) + ); + } + + function _drawText( + uint256 x, + uint256 y, + string memory fill, + string memory fontSize, + string memory fontFamily, + string memory textAnchor, + string memory text + ) internal pure returns (string memory) { + return string.concat( + '', + text, + "" + ); + } + + function _calculateCountdown(uint256 deadline) + internal + view + returns (string memory) + { + uint256 timeLeft = deadline - block.timestamp; + uint256 daysLeft = timeLeft / 86400; + uint256 hoursLeft = (timeLeft % 86400) / 3600; + uint256 minutesLeft = (timeLeft % 3600) / 60; + uint256 secondsLeft = timeLeft % 60; + + // TODO: Fix the plurals + if (daysLeft >= 1) { + return (string.concat("Expires in ", daysLeft.toString(), " days")); + } + + if (hoursLeft >= 1) { + return + (string.concat("Expires in ", hoursLeft.toString(), " hours")); + } + + if (minutesLeft >= 1) { + return ( + string.concat("Expires in ", minutesLeft.toString(), " minutes") + ); + } + + return + (string.concat("Expires in ", secondsLeft.toString(), " seconds")); + } } + +/* + + + + + + + + + + + + + + + + + + + + + + + + + + + + USDT - USDC + Expires in 66 days + + Spot Price + 0.99 USDC + + Strike Price + 1.00 USDC + + Asset Reserve + 435,235 USDT + + Quote Reserve + 452,673 USDC + + Pool Valuation + 883,555.65 USDC + +*/ From 91102a611d8d50baf2a423324aabc35180746c57 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 21 Aug 2023 19:00:19 +0400 Subject: [PATCH 18/48] feat: refactor NFT to use HTML (instead of SVG) --- contracts/PositionRenderer.sol | 532 ++++++++++++++++++++------------- 1 file changed, 325 insertions(+), 207 deletions(-) diff --git a/contracts/PositionRenderer.sol b/contracts/PositionRenderer.sol index 9157bd9e..973668ab 100644 --- a/contracts/PositionRenderer.sol +++ b/contracts/PositionRenderer.sol @@ -26,6 +26,7 @@ contract PositionRenderer { } struct Pool { + uint256 poolId; uint128 virtualX; uint128 virtualY; uint16 feeBasisPoints; @@ -33,6 +34,7 @@ contract PositionRenderer { address controller; address strategy; uint256 spotPriceWad; + bool hasDefaultStrategy; } struct Config { @@ -50,7 +52,7 @@ contract PositionRenderer { } string private constant PRIMITIVE_LOGO = - ''; + ''; function uri(uint256 id) external view returns (string memory) { Properties memory properties = _getProperties(id); @@ -62,8 +64,8 @@ contract PositionRenderer { abi.encodePacked( '{"name":"', _generateName(properties), - '","image":"', - _generateImage(properties), + '","animation_url":"', + _generateHTML(properties), '","license":"MIT","creator":"primitive.eth",', '"description":"This NFT represents a liquidity position in a Portfolio pool. The owner of this NFT can modify or redeem this position.\\n\\n', unicode"⚠️ WARNING: Transferring this NFT makes the new recipient the owner of the position.", @@ -81,6 +83,7 @@ contract PositionRenderer { ); } + /* function _generateImage(Properties memory properties) private view @@ -91,6 +94,7 @@ contract PositionRenderer { Base64.encode(bytes(_generateSVG(properties))) ); } + */ function _getPair(uint256 id) internal view returns (Pair memory) { ( @@ -127,13 +131,16 @@ contract PositionRenderer { uint256 spotPriceWad = IPortfolio(msg.sender).getSpotPrice(uint64(id)); return Pool({ + poolId: id, virtualX: virtualX, virtualY: virtualY, feeBasisPoints: feeBasisPoints, priorityFeeBasisPoints: priorityFeeBasisPoints, controller: controller, strategy: strategy, - spotPriceWad: spotPriceWad + spotPriceWad: spotPriceWad, + hasDefaultStrategy: strategy + == IPortfolio(msg.sender).DEFAULT_STRATEGY() }); } @@ -254,20 +261,42 @@ contract PositionRenderer { ); } - function _generateSVG(Properties memory properties) + function _generateHTML(Properties memory properties) private view returns (string memory) { - return string.concat( - '', - _generateSVGNoise(), - _generateSVGGradient(), - '' - '', - PRIMITIVE_LOGO, + string memory color0 = _generateColor(properties.pool.poolId / 10); + string memory color1 = _generateColor(properties.pool.poolId * 10); + + string memory data = string.concat( + " ', _generateStats(properties), - "" + _generateHTMLFooter(properties), + "" + ); + + return + string.concat("data:text/html;base64,", Base64.encode(bytes(data))); + } + + function _generateStat( + string memory label, + string memory amount, + bool alignRight + ) private pure returns (string memory) { + return string.concat( + "', + label, + "
", + amount, + "" ); } @@ -277,247 +306,332 @@ contract PositionRenderer { returns (string memory) { return string.concat( - _generateSVGTitle(properties), - _generateSVGSpotPrice(properties), - _generateSVGStrikePrice(properties), - _generateSVGReserves(properties), - _generateSVGPoolValuation(properties), - _generateSVGSwapFee(properties) - ); - } - - function _generateSVGNoise() internal pure returns (string memory) { - return - ' '; - } - - function _generateSVGGradient() internal pure returns (string memory) { - return string.concat( - '' + '', + "", + _generateHTMLTitle(properties), + "", + _generateHTMLSpotPrice(properties), + _generateHTMLStrikePrice(properties), + "", + _generateHTMLAssetReserves(properties), + _generateHTMLQuoteReserves(properties), + "", + _generateHTMLPoolValuation(properties), + _generateHTMLSwapFee(properties), + "
", + PRIMITIVE_LOGO, + "
" ); } - function _generateSVGTitle(Properties memory properties) + function _generateHTMLTitle(Properties memory properties) internal view returns (string memory) { return string.concat( - _drawText( - 550, - 75, - "#fff", - "3.25em", - "monospace", - "end", + _generateStat( string.concat( properties.pair.assetSymbol, - " - ", + "-", properties.pair.quoteSymbol - ) - ), - _drawText( - 550, - 100, - "#ffffff80", - "1.75em", - "monospace", - "end", + ), properties.config.isPerpetual - ? "Never expires" + ? "Perpetual pool" : _calculateCountdown( properties.config.creationTimestamp + properties.config.durationSeconds - ) + ), + true ) ); } - function _generateSVGSpotPrice(Properties memory properties) + function _generateHTMLSpotPrice(Properties memory properties) internal pure returns (string memory) { return string.concat( - _drawText( - 50, - 200, - "#ffffff80", - "1.75em", - "monospace", - "start", - "Spot Price" - ), - _drawText( - 50, - 240, - "#fff", - "2.5em", - "monospace", - "start", + _generateStat( + "Spot Price", string.concat( - properties.pool.spotPriceWad.toString(), + abbreviateAmount( + properties.pool.spotPriceWad, + properties.pair.quoteDecimals + ), " ", properties.pair.quoteSymbol - ) + ), + false ) ); } - function _generateSVGStrikePrice(Properties memory properties) + function _generateHTMLStrikePrice(Properties memory properties) internal pure returns (string memory) { return string.concat( - _drawText( - 325, - 200, - "#ffffff80", - "1.75em", - "monospace", - "start", - "Strike Price" - ), - _drawText( - 325, - 240, - "#fff", - "2.5em", - "monospace", - "start", + _generateStat( + "Strike Price", string.concat( - properties.config.strikePriceWad.toString(), + abbreviateAmount( + properties.config.strikePriceWad, + properties.pair.quoteDecimals + ), " ", properties.pair.quoteSymbol - ) + ), + false ) ); } - function _generateSVGReserves(Properties memory properties) + function _generateHTMLAssetReserves(Properties memory properties) internal pure returns (string memory) { - return ( - string.concat( - _drawText( - 50, - 320, - "#ffffff80", - "1.75em", - "monospace", - "start", - "Asset Reserve" + return string.concat( + _generateStat( + "Asset Reserves", + string.concat( + abbreviateAmount( + properties.pool.virtualX, properties.pair.assetDecimals + ), + " ", + properties.pair.assetSymbol ), - _drawText( - 50, - 360, - "#fff", - "2.5em", - "monospace", - "start", - string.concat( - properties.pool.virtualX.toString(), - " ", - properties.pair.assetSymbol - ) + false + ) + ); + } + + function _generateHTMLQuoteReserves(Properties memory properties) + internal + pure + returns (string memory) + { + return string.concat( + _generateStat( + "Asset Reserves", + string.concat( + abbreviateAmount( + properties.pool.virtualY, properties.pair.quoteDecimals + ), + " ", + properties.pair.quoteSymbol ), - _drawText( - 325, - 320, - "#ffffff80", - "1.75em", - "monospace", - "start", - "Quote Reserve" + false + ) + ); + } + + function _generateHTMLPoolValuation(Properties memory properties) + internal + pure + returns (string memory) + { + uint256 poolValuation = ( + properties.pool.virtualX * properties.pool.spotPriceWad + ) / properties.pair.quoteDecimals + properties.pool.virtualY; + + return string.concat( + _generateStat( + "Pool Valuation", + string.concat( + abbreviateAmount( + poolValuation, properties.pair.quoteDecimals + ), + " ", + properties.pair.quoteSymbol ), - _drawText( - 325, - 360, - "#fff", - "2.5em", - "monospace", - "start", - string.concat( - properties.pool.virtualY.toString(), - " ", - properties.pair.quoteSymbol - ) - ) + false ) ); } - function _generateSVGPoolValuation(Properties memory properties) + function _generateHTMLSwapFee(Properties memory properties) internal pure returns (string memory) { - return ( - string.concat( - _drawText( - 50, - 440, - "#ffffff80", - "1.75em", - "monospace", - "start", - "Pool Valuation" + return string.concat( + _generateStat( + "Swap Fee", + string.concat( + abbreviateAmount(properties.pool.feeBasisPoints, 4), " %" ), - _drawText( - 50, - 480, - "#fff", - "2.5em", - "monospace", - "start", - string.concat( - properties.config.strikePriceWad.toString(), - " ", - properties.pair.quoteSymbol - ) - ) + false ) ); } - function _generateSVGSwapFee(Properties memory properties) + function _generateHTMLFooter(Properties memory properties) internal pure returns (string memory) { + string memory controlledLabel = properties.pool.controller == address(0) + ? "This pool is not controlled" + : string.concat( + "This pool is controlled by ", + properties.pool.controller.toHexString() + ); + return ( string.concat( - _drawText( - 325, - 440, - "#ffffff80", - "1.75em", - "monospace", - "start", - "Swap Fee" - ), - _drawText( - 325, - 480, - "#fff", - "2.5em", - "monospace", - "start", - string.concat( - properties.pool.feeBasisPoints.toString(), " %" - ) - ) + '" ) ); } + function _calculateCountdown(uint256 deadline) + internal + view + returns (string memory) + { + uint256 timeLeft = deadline - block.timestamp; + uint256 daysLeft = timeLeft / 86400; + uint256 hoursLeft = (timeLeft % 86400) / 3600; + uint256 minutesLeft = (timeLeft % 3600) / 60; + uint256 secondsLeft = timeLeft % 60; + + // TODO: Fix the plurals + if (daysLeft >= 1) { + return (string.concat("Expires in ", daysLeft.toString(), " days")); + } + + if (hoursLeft >= 1) { + return + (string.concat("Expires in ", hoursLeft.toString(), " hours")); + } + + if (minutesLeft >= 1) { + return ( + string.concat("Expires in ", minutesLeft.toString(), " minutes") + ); + } + + return + (string.concat("Expires in ", secondsLeft.toString(), " seconds")); + } + + /// @dev Escape character for "≥". + string internal constant SIGN_GE = "≥"; + + /// @dev Escape character for ">". + string internal constant SIGN_GT = ">"; + + /// @dev Escape character for "<". + string internal constant SIGN_LT = "<"; + + /// @notice Creates an abbreviated representation of the provided amount, rounded down and prefixed with ">= ". + /// @dev The abbreviation uses these suffixes: + /// - "K" for thousands + /// - "M" for millions + /// - "B" for billions + /// - "T" for trillions + /// For example, if the input is 1,234,567, the output is ">= 1.23M". + /// @param amount The amount to abbreviate, denoted in units of `decimals`. + /// @param decimals The number of decimals to assume when abbreviating the amount. + /// @return abbreviation The abbreviated representation of the provided amount, as a string. + function abbreviateAmount( + uint256 amount, + uint256 decimals + ) internal pure returns (string memory) { + if (amount == 0) { + return "0"; + } + + uint256 truncatedAmount; + unchecked { + truncatedAmount = decimals == 0 ? amount : amount / 10 ** decimals; + } + + // Return dummy values when the truncated amount is either very small or very big. + if (truncatedAmount < 1) { + return string.concat(SIGN_LT, " 1"); + } else if (truncatedAmount >= 1e15) { + return string.concat(SIGN_GT, " 999.99T"); + } + + string[5] memory suffixes = ["", "K", "M", "B", "T"]; + uint256 fractionalAmount; + uint256 suffixIndex = 0; + + // Truncate repeatedly until the amount is less than 1000. + unchecked { + while (truncatedAmount >= 1000) { + fractionalAmount = (truncatedAmount / 10) % 100; // keep the first two digits after the decimal point + truncatedAmount /= 1000; + suffixIndex += 1; + } + } + + // Concatenate the calculated parts to form the final string. + string memory prefix = string.concat(SIGN_GE, " "); + string memory wholePart = truncatedAmount.toString(); + string memory fractionalPart = + stringifyFractionalAmount(fractionalAmount); + return string.concat( + prefix, wholePart, fractionalPart, suffixes[suffixIndex] + ); + } + + /// @notice Converts the provided fractional amount to a string prefixed by a dot. + /// @param fractionalAmount A numerical value with 2 implied decimals. + function stringifyFractionalAmount(uint256 fractionalAmount) + internal + pure + returns (string memory) + { + // Return the empty string if the fractional amount is zero. + if (fractionalAmount == 0) { + return ""; + } + // Add a leading zero if the fractional part is less than 10, e.g. for "1", this function returns ".01%". + else if (fractionalAmount < 10) { + return string.concat(".0", fractionalAmount.toString()); + } + // Otherwise, stringify the fractional amount simply. + else { + return string.concat(".", fractionalAmount.toString()); + } + } + + function _generateColor(uint256 seed) + internal + pure + returns (string memory) + { + return string.concat( + "rgb(", + _generateNumber(seed, 255).toString(), + ",", + _generateNumber(seed + 1, 255).toString(), + ",", + _generateNumber(seed + 2, 255).toString(), + ")" + ); + } + + function _generateNumber( + uint256 seed, + uint256 max + ) internal pure returns (uint256) { + return uint256(keccak256(abi.encodePacked(seed))) % max; + } + + /* function _drawText( uint256 x, uint256 y, @@ -546,36 +660,40 @@ contract PositionRenderer { ); } - function _calculateCountdown(uint256 deadline) - internal + function _generateSVGNoise() internal pure returns (string memory) { + return + ' '; + } + + function _generateSVGGradient() internal pure returns (string memory) { + return string.concat( + '' + ); + } + + function _generateSVG(Properties memory properties) + private view returns (string memory) { - uint256 timeLeft = deadline - block.timestamp; - uint256 daysLeft = timeLeft / 86400; - uint256 hoursLeft = (timeLeft % 86400) / 3600; - uint256 minutesLeft = (timeLeft % 3600) / 60; - uint256 secondsLeft = timeLeft % 60; - - // TODO: Fix the plurals - if (daysLeft >= 1) { - return (string.concat("Expires in ", daysLeft.toString(), " days")); - } - - if (hoursLeft >= 1) { - return - (string.concat("Expires in ", hoursLeft.toString(), " hours")); - } - - if (minutesLeft >= 1) { - return ( - string.concat("Expires in ", minutesLeft.toString(), " minutes") - ); - } - - return - (string.concat("Expires in ", secondsLeft.toString(), " seconds")); + return string.concat( + '', + _generateSVGNoise(), + _generateSVGGradient(), + '' + '', + PRIMITIVE_LOGO, + _generateStats(properties), + _generateSVGFooter(properties), + "" + ); } + + */ } /* From 84a19b6e305ab1cedcad90386aaf6f2a4d50f7b6 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 30 Aug 2023 10:13:12 +0400 Subject: [PATCH 19/48] feat: add custom strings library --- contracts/libraries/StringsLib.sol | 92 ++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 contracts/libraries/StringsLib.sol diff --git a/contracts/libraries/StringsLib.sol b/contracts/libraries/StringsLib.sol new file mode 100644 index 00000000..94303569 --- /dev/null +++ b/contracts/libraries/StringsLib.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.19; + +import { Math } from "openzeppelin/utils/math/Math.sol"; + +/** + * @dev Modified version of: + * OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol) + */ +library Strings { + bytes16 private constant _SYMBOLS = "0123456789abcdef"; + uint8 private constant _ADDRESS_LENGTH = 20; + + /** + * @dev The `value` string doesn't fit in the specified `length`. + */ + error StringsInsufficientHexLength(uint256 value, uint256 length); + + /** + * @dev Converts a `uint256` to its ASCII `string` decimal representation. + */ + function toString(uint256 value) internal pure returns (string memory) { + unchecked { + uint256 length = Math.log10(value) + 1; + string memory buffer = new string(length); + uint256 ptr; + /// @solidity memory-safe-assembly + assembly { + ptr := add(buffer, add(32, length)) + } + while (true) { + ptr--; + /// @solidity memory-safe-assembly + assembly { + mstore8(ptr, byte(mod(value, 10), _SYMBOLS)) + } + value /= 10; + if (value == 0) break; + } + return buffer; + } + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation. + */ + function toHexString(uint256 value) internal pure returns (string memory) { + unchecked { + return toHexString(value, Math.log256(value) + 1); + } + } + + /** + * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length. + */ + function toHexString( + uint256 value, + uint256 length + ) internal pure returns (string memory) { + uint256 localValue = value; + bytes memory buffer = new bytes(2 * length + 2); + buffer[0] = "0"; + buffer[1] = "x"; + for (uint256 i = 2 * length + 1; i > 1; --i) { + buffer[i] = _SYMBOLS[localValue & 0xf]; + localValue >>= 4; + } + if (localValue != 0) { + revert StringsInsufficientHexLength(value, length); + } + return string(buffer); + } + + /** + * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation. + */ + function toHexString(address addr) internal pure returns (string memory) { + return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH); + } + + function toHexColor(bytes3 value) internal pure returns (string memory) { + bytes memory result = new bytes(6); + + for (uint256 i = 0; i < 3; i++) { + result[i * 2] = _SYMBOLS[uint8(value[i] >> 4)]; + result[i * 2 + 1] = _SYMBOLS[uint8(value[i] & 0x0F)]; + } + + return string.concat("#", string(result)); + } +} From 066707f966e0ae82a74829bd9dde8b751700724c Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 30 Aug 2023 16:20:32 +0400 Subject: [PATCH 20/48] test: add controlled pool uri test --- test/TestPortfolioUri.t.sol | 131 ++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/test/TestPortfolioUri.t.sol b/test/TestPortfolioUri.t.sol index 9dca2951..80e8ebbd 100644 --- a/test/TestPortfolioUri.t.sol +++ b/test/TestPortfolioUri.t.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.4; import "openzeppelin/utils/Base64.sol"; import "solmate/tokens/ERC1155.sol"; +import "../contracts/libraries/AssemblyLib.sol"; import "./Setup.sol"; contract TestPortfolioUri is Setup { @@ -20,6 +21,136 @@ contract TestPortfolioUri is Setup { console.log(uri); } + function test_metadata() public { + address asset = address(new MockERC20("Ethereum", "ETH", 18)); + address quote = address(new MockERC20("USD Coin", "USDC", 6)); + uint24 pairId = subject().createPair(asset, quote); + + uint256 strikePriceWad = AssemblyLib.scaleToWad(2000 * 10 ** 6, 6); + uint256 volatilityBasisPoints = 1_000; + uint256 durationSeconds = 86_400 * 7; + bool isPerpetual = false; + uint256 priceWad = AssemblyLib.scaleToWad(2000 * 10 ** 6, 6); + + (bytes memory strategyData, uint256 initialX, uint256 initialY) = + INormalStrategy(subject().DEFAULT_STRATEGY()).getStrategyData( + strikePriceWad, + volatilityBasisPoints, + durationSeconds, + isPerpetual, + priceWad + ); + + uint64 poolId = subject().createPool( + pairId, + initialX, + initialY, + 100, + 0, + address(0), + subject().DEFAULT_STRATEGY(), + strategyData + ); + + uint256 amount0 = 10 ether; + uint256 amount1 = 20_000 * 10 ** 6; + + MockERC20(asset).mint(address(this), 100 ether); + MockERC20(asset).approve(address(subject()), 100 ether); + MockERC20(quote).mint(address(this), 200_000 * 10 ** 6); + MockERC20(quote).approve(address(subject()), 200_000 * 10 ** 6); + + uint128 deltaLiquidity = + subject().getMaxLiquidity(poolId, amount0, amount1); + + console.log(deltaLiquidity); + + (uint128 deltaAsset, uint128 deltaQuote) = + subject().getLiquidityDeltas(poolId, int128(deltaLiquidity)); + + (uint256 usedDeltaAsset, uint256 usedDeltaQuote) = subject().allocate( + false, + address(this), + poolId, + deltaLiquidity, + uint128(amount0), + uint128(amount1) + ); + + console.log(deltaAsset); + console.log(deltaQuote); + console.log("Used:", usedDeltaAsset); + console.log("Used:", usedDeltaQuote); + + string memory uri = ERC1155(address(subject())).uri(poolId); + console.log(uri); + } + + function test_metadata_controlled_pool() public { + address asset = address(new MockERC20("Ethereum", "ETH", 18)); + address quote = address(new MockERC20("USD Coin", "USDC", 6)); + uint24 pairId = subject().createPair(asset, quote); + + uint256 strikePriceWad = AssemblyLib.scaleToWad(2000 * 10 ** 6, 6); + uint256 volatilityBasisPoints = 1_000; + uint256 durationSeconds = 86_400 * 7; + bool isPerpetual = false; + uint256 priceWad = AssemblyLib.scaleToWad(2000 * 10 ** 6, 6); + + (bytes memory strategyData, uint256 initialX, uint256 initialY) = + INormalStrategy(subject().DEFAULT_STRATEGY()).getStrategyData( + strikePriceWad, + volatilityBasisPoints, + durationSeconds, + isPerpetual, + priceWad + ); + + uint64 poolId = subject().createPool( + pairId, + initialX, + initialY, + 200, + 100, + address(this), + subject().DEFAULT_STRATEGY(), + strategyData + ); + + uint256 amount0 = 10 ether; + uint256 amount1 = 20_000 * 10 ** 6; + + MockERC20(asset).mint(address(this), 100 ether); + MockERC20(asset).approve(address(subject()), 100 ether); + MockERC20(quote).mint(address(this), 200_000 * 10 ** 6); + MockERC20(quote).approve(address(subject()), 200_000 * 10 ** 6); + + uint128 deltaLiquidity = + subject().getMaxLiquidity(poolId, amount0, amount1); + + console.log(deltaLiquidity); + + (uint128 deltaAsset, uint128 deltaQuote) = + subject().getLiquidityDeltas(poolId, int128(deltaLiquidity)); + + (uint256 usedDeltaAsset, uint256 usedDeltaQuote) = subject().allocate( + false, + address(this), + poolId, + deltaLiquidity, + uint128(amount0), + uint128(amount1) + ); + + console.log(deltaAsset); + console.log(deltaQuote); + console.log("Used:", usedDeltaAsset); + console.log("Used:", usedDeltaQuote); + + string memory uri = ERC1155(address(subject())).uri(poolId); + console.log(uri); + } + function test_balanceOf_allocating_sets_balance() public defaultConfig From 827420df694ef286700b98ce2f1b886fd167c5a2 Mon Sep 17 00:00:00 2001 From: clemlak Date: Wed, 30 Aug 2023 16:20:44 +0400 Subject: [PATCH 21/48] feat: update HTML rendering --- contracts/PositionRenderer.sol | 259 ++++++++++----------------------- 1 file changed, 79 insertions(+), 180 deletions(-) diff --git a/contracts/PositionRenderer.sol b/contracts/PositionRenderer.sol index 973668ab..91c56a3e 100644 --- a/contracts/PositionRenderer.sol +++ b/contracts/PositionRenderer.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.4; import "openzeppelin/utils/Base64.sol"; -import "openzeppelin/utils/Strings.sol"; import "solmate/tokens/ERC20.sol"; import "solmate/utils/SafeCastLib.sol"; + +import "./libraries/StringsLib.sol"; import "./interfaces/IPortfolio.sol"; import "./interfaces/IStrategy.sol"; import "./strategies/NormalStrategy.sol"; @@ -52,7 +53,12 @@ contract PositionRenderer { } string private constant PRIMITIVE_LOGO = - ''; + ''; + + string private constant STYLE_0 = + ""; function uri(uint256 id) external view returns (string memory) { Properties memory properties = _getProperties(id); @@ -83,19 +89,6 @@ contract PositionRenderer { ); } - /* - function _generateImage(Properties memory properties) - private - view - returns (string memory) - { - return string.concat( - "data:image/svg+xml;base64,", - Base64.encode(bytes(_generateSVG(properties))) - ); - } - */ - function _getPair(uint256 id) internal view returns (Pair memory) { ( address tokenAsset, @@ -223,6 +216,15 @@ contract PositionRenderer { returns (string memory) { return string.concat( + '"asset_reserves":"', + (properties.pool.virtualX / 1).toString(), + '",', + '"quote_reserves":"', + (properties.pool.virtualY / 1).toString(), + '",', + '"spot_price_wad":"', + (properties.pool.spotPriceWad / 10 ** 18).toString(), + '",', '"fee_basis_points":"', properties.pool.feeBasisPoints.toString(), '",', @@ -245,7 +247,7 @@ contract PositionRenderer { { return string.concat( '"strike_price_wad":"', - properties.config.strikePriceWad.toString(), + (properties.config.strikePriceWad / 10 ** 18).toString(), '",', '"volatility_basis_points":"', properties.config.volatilityBasisPoints.toString(), @@ -266,15 +268,34 @@ contract PositionRenderer { view returns (string memory) { - string memory color0 = _generateColor(properties.pool.poolId / 10); - string memory color1 = _generateColor(properties.pool.poolId * 10); + string memory color0 = Strings.toHexColor( + bytes3( + keccak256(abi.encode((properties.pool.poolId >> 232) << 232)) + ) + ); + string memory color1 = Strings.toHexColor( + bytes3(keccak256(abi.encode(properties.pool.poolId << 232))) + ); + + string memory title = string.concat( + properties.pair.assetSymbol, + "-", + properties.pair.quoteSymbol, + " Portfolio LP" + ); string memory data = string.concat( - " ', + STYLE_1, + "", + '
', + '', _generateStats(properties), _generateHTMLFooter(properties), "" @@ -286,17 +307,23 @@ contract PositionRenderer { function _generateStat( string memory label, - string memory amount, - bool alignRight + string memory amount + ) private pure returns (string memory) { + return string.concat( + '', label, "
", amount, "" + ); + } + + function _generateTitle( + string memory label, + string memory amount ) private pure returns (string memory) { return string.concat( - "', + '', label, - "
", + '
', amount, - "" + "" ); } @@ -306,7 +333,7 @@ contract PositionRenderer { returns (string memory) { return string.concat( - '', + '
', "", @@ -330,7 +357,7 @@ contract PositionRenderer { returns (string memory) { return string.concat( - _generateStat( + _generateTitle( string.concat( properties.pair.assetSymbol, "-", @@ -341,8 +368,7 @@ contract PositionRenderer { : _calculateCountdown( properties.config.creationTimestamp + properties.config.durationSeconds - ), - true + ) ) ); } @@ -357,13 +383,12 @@ contract PositionRenderer { "Spot Price", string.concat( abbreviateAmount( - properties.pool.spotPriceWad, + properties.pool.spotPriceWad / 10 ** 18, properties.pair.quoteDecimals ), " ", properties.pair.quoteSymbol - ), - false + ) ) ); } @@ -378,13 +403,12 @@ contract PositionRenderer { "Strike Price", string.concat( abbreviateAmount( - properties.config.strikePriceWad, + properties.config.strikePriceWad / 10 ** 18, properties.pair.quoteDecimals ), " ", properties.pair.quoteSymbol - ), - false + ) ) ); } @@ -399,12 +423,12 @@ contract PositionRenderer { "Asset Reserves", string.concat( abbreviateAmount( - properties.pool.virtualX, properties.pair.assetDecimals + properties.pool.virtualX / 10 ** 18, + properties.pair.assetDecimals ), " ", properties.pair.assetSymbol - ), - false + ) ) ); } @@ -419,12 +443,12 @@ contract PositionRenderer { "Asset Reserves", string.concat( abbreviateAmount( - properties.pool.virtualY, properties.pair.quoteDecimals + properties.pool.virtualY / 10 ** 18, + properties.pair.quoteDecimals ), " ", properties.pair.quoteSymbol - ), - false + ) ) ); } @@ -443,12 +467,11 @@ contract PositionRenderer { "Pool Valuation", string.concat( abbreviateAmount( - poolValuation, properties.pair.quoteDecimals + poolValuation / 10 ** 18, properties.pair.quoteDecimals ), " ", properties.pair.quoteSymbol - ), - false + ) ) ); } @@ -463,8 +486,7 @@ contract PositionRenderer { "Swap Fee", string.concat( abbreviateAmount(properties.pool.feeBasisPoints, 4), " %" - ), - false + ) ) ); } @@ -483,7 +505,7 @@ contract PositionRenderer { return ( string.concat( - '" + '" + ); + } + + function _generatePercentStat( + string memory label, + uint256 amount + ) private pure returns (string memory) { + return string.concat( + '" ); } @@ -382,17 +407,11 @@ contract PositionRenderer { return string.concat( _generateStat( "Spot Price", - string.concat( - StringsLib.toFormatAmount( - AssemblyLib.scaleFromWadDown( - properties.pool.spotPriceWad, - properties.pair.quoteDecimals - ), - properties.pair.quoteDecimals - ), - " ", - properties.pair.quoteSymbol - ) + AssemblyLib.scaleFromWadDown( + properties.pool.spotPriceWad, properties.pair.quoteDecimals + ), + properties.pair.quoteDecimals, + properties.pair.quoteSymbol ) ); } @@ -405,17 +424,12 @@ contract PositionRenderer { return string.concat( _generateStat( "Strike Price", - string.concat( - StringsLib.toFormatAmount( - AssemblyLib.scaleFromWadDown( - properties.config.strikePriceWad, - properties.pair.quoteDecimals - ), - properties.pair.quoteDecimals - ), - " ", - properties.pair.quoteSymbol - ) + AssemblyLib.scaleFromWadDown( + properties.config.strikePriceWad, + properties.pair.quoteDecimals + ), + properties.pair.quoteDecimals, + properties.pair.quoteSymbol ) ); } @@ -428,17 +442,11 @@ contract PositionRenderer { return string.concat( _generateStat( "Asset Reserves", - string.concat( - StringsLib.toFormatAmount( - AssemblyLib.scaleFromWadDown( - properties.pool.virtualX, - properties.pair.assetDecimals - ), - properties.pair.assetDecimals - ), - " ", - properties.pair.assetSymbol - ) + AssemblyLib.scaleFromWadDown( + properties.pool.virtualX, properties.pair.assetDecimals + ), + properties.pair.assetDecimals, + properties.pair.assetSymbol ) ); } @@ -451,17 +459,11 @@ contract PositionRenderer { return string.concat( _generateStat( "Asset Reserves", - string.concat( - StringsLib.toFormatAmount( - AssemblyLib.scaleFromWadDown( - properties.pool.virtualY, - properties.pair.quoteDecimals - ), - properties.pair.quoteDecimals - ), - " ", - properties.pair.quoteSymbol - ) + AssemblyLib.scaleFromWadDown( + properties.pool.virtualY, properties.pair.quoteDecimals + ), + properties.pair.quoteDecimals, + properties.pair.quoteSymbol ) ); } @@ -484,13 +486,9 @@ contract PositionRenderer { return string.concat( _generateStat( "Pool Valuation", - string.concat( - StringsLib.toFormatAmount( - poolValuation, properties.pair.quoteDecimals - ), - " ", - properties.pair.quoteSymbol - ) + poolValuation, + properties.pair.quoteDecimals, + properties.pair.quoteSymbol ) ); } @@ -501,9 +499,7 @@ contract PositionRenderer { returns (string memory) { return string.concat( - _generateStat( - "Swap Fee", properties.pool.feeBasisPoints.toStringPercent() - ) + _generatePercentStat("Swap Fee", properties.pool.feeBasisPoints) ); } From 2ddf3c5453486dd787bc98fd3570a3f8adfe19a8 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 1 Sep 2023 18:03:25 +0400 Subject: [PATCH 29/48] feat: change HTML generation --- contracts/PositionRenderer.sol | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/contracts/PositionRenderer.sol b/contracts/PositionRenderer.sol index 2eef125b..6a9e382d 100644 --- a/contracts/PositionRenderer.sol +++ b/contracts/PositionRenderer.sol @@ -71,8 +71,8 @@ contract PositionRenderer { abi.encodePacked( '{"name":"', _generateName(properties), - '","animation_url":"', - _generateHTML(properties), + '","animation_url":"data:text/html;base64,', + Base64.encode(bytes(_generateHTML(properties))), '","license":"MIT","creator":"primitive.eth",', '"description":"This NFT represents a liquidity position in a Portfolio pool. The owner of this NFT can modify or redeem this position.\\n\\n', unicode"⚠️ WARNING: Transferring this NFT makes the new recipient the owner of the position.", @@ -285,7 +285,7 @@ contract PositionRenderer { " Portfolio LP" ); - string memory data = string.concat( + return string.concat( '', title, "", @@ -301,9 +301,6 @@ contract PositionRenderer { _generateHTMLFooter(properties), "" ); - - return - string.concat("data:text/html;base64,", Base64.encode(bytes(data))); } function _generateStat( From 11e9c94b86c59ca2224a72ada93c5777ea0695d5 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 1 Sep 2023 18:03:37 +0400 Subject: [PATCH 30/48] test: update uri and balanceOf tests --- test/TestPortfolioBalanceOf.t copy.sol | 68 +++++++ test/TestPortfolioUri.t.sol | 236 ------------------------- test/TestPositionRendererUri.t.sol | 166 +++++++++++++++++ 3 files changed, 234 insertions(+), 236 deletions(-) create mode 100644 test/TestPortfolioBalanceOf.t copy.sol delete mode 100644 test/TestPortfolioUri.t.sol create mode 100644 test/TestPositionRendererUri.t.sol diff --git a/test/TestPortfolioBalanceOf.t copy.sol b/test/TestPortfolioBalanceOf.t copy.sol new file mode 100644 index 00000000..396941fc --- /dev/null +++ b/test/TestPortfolioBalanceOf.t copy.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.4; + +import "solmate/tokens/ERC1155.sol"; +import "../contracts/libraries/AssemblyLib.sol"; +import "./Setup.sol"; +import "../contracts/strategies/NormalStrategy.sol"; + +contract TestPortfolioBalanceOf is Setup { + function test_balanceOf_allocating_increases_balance() + public + defaultConfig + useActor + usePairTokens(10 ether) + { + uint128 liquidity = 1 ether; + + subject().allocate( + false, + address(this), + ghost().poolId, + liquidity, + type(uint128).max, + type(uint128).max + ); + + subject().allocate( + false, + address(this), + ghost().poolId, + liquidity, + type(uint128).max, + type(uint128).max + ); + + assertEq( + ERC1155(address(subject())).balanceOf(address(this), ghost().poolId), + liquidity * 2 - BURNED_LIQUIDITY + ); + } + + function test_balanceOf_deallocating_reduces_balance() + public + defaultConfig + useActor + usePairTokens(10 ether) + { + uint128 liquidity = 1 ether; + + subject().allocate( + false, + address(this), + ghost().poolId, + liquidity, + type(uint128).max, + type(uint128).max + ); + + subject().deallocate( + false, ghost().poolId, uint128(liquidity - BURNED_LIQUIDITY), 0, 0 + ); + + assertEq( + ERC1155(address(subject())).balanceOf(address(this), ghost().poolId), + 0 + ); + } +} diff --git a/test/TestPortfolioUri.t.sol b/test/TestPortfolioUri.t.sol deleted file mode 100644 index 3fb74e1a..00000000 --- a/test/TestPortfolioUri.t.sol +++ /dev/null @@ -1,236 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -pragma solidity ^0.8.4; - -import "solmate/tokens/ERC1155.sol"; -import "../contracts/libraries/AssemblyLib.sol"; -import "./Setup.sol"; - -contract TestPortfolioUri is Setup { - function test_uri() public defaultConfig useActor usePairTokens(10 ether) { - subject().allocate( - false, - address(this), - ghost().poolId, - 1 ether, - type(uint128).max, - type(uint128).max - ); - - string memory uri = ERC1155(address(subject())).uri(ghost().poolId); - console.log(uri); - } - - function test_metadata() public { - address asset = address(new MockERC20("Ethereum", "ETH", 18)); - address quote = address(new MockERC20("USD Coin", "USDC", 6)); - uint24 pairId = subject().createPair(asset, quote); - - uint256 strikePriceWad = AssemblyLib.scaleToWad(2000 * 10 ** 6, 6); - uint256 volatilityBasisPoints = 1_000; - uint256 durationSeconds = 86_400 * 3; - bool isPerpetual = false; - uint256 priceWad = AssemblyLib.scaleToWad(2000 * 10 ** 6, 6); - - (bytes memory strategyData, uint256 initialX, uint256 initialY) = - INormalStrategy(subject().DEFAULT_STRATEGY()).getStrategyData( - strikePriceWad, - volatilityBasisPoints, - durationSeconds, - isPerpetual, - priceWad - ); - - uint64 poolId = subject().createPool( - pairId, - initialX, - initialY, - 100, - 0, - address(0), - subject().DEFAULT_STRATEGY(), - strategyData - ); - - uint256 amount0 = 10 ether; - uint256 amount1 = 20_000 * 10 ** 6; - - MockERC20(asset).mint(address(this), 100 ether); - MockERC20(asset).approve(address(subject()), 100 ether); - MockERC20(quote).mint(address(this), 200_000 * 10 ** 6); - MockERC20(quote).approve(address(subject()), 200_000 * 10 ** 6); - - uint128 deltaLiquidity = - subject().getMaxLiquidity(poolId, amount0, amount1); - - console.log(deltaLiquidity); - - (uint128 deltaAsset, uint128 deltaQuote) = - subject().getLiquidityDeltas(poolId, int128(deltaLiquidity)); - - (uint256 usedDeltaAsset, uint256 usedDeltaQuote) = subject().allocate( - false, - address(this), - poolId, - deltaLiquidity, - uint128(amount0), - uint128(amount1) - ); - - console.log(deltaAsset); - console.log(deltaQuote); - console.log("Used:", usedDeltaAsset); - console.log("Used:", usedDeltaQuote); - - string memory uri = ERC1155(address(subject())).uri(poolId); - console.log(uri); - } - - /* - function test_metadata_controlled_pool() public { - address asset = address(new MockERC20("Ethereum", "ETH", 18)); - address quote = address(new MockERC20("USD Coin", "USDC", 6)); - uint24 pairId = subject().createPair(asset, quote); - - uint256 strikePriceWad = AssemblyLib.scaleToWad(2000 * 10 ** 6, 6); - uint256 volatilityBasisPoints = 1_000; - uint256 durationSeconds = 86_400 * 7; - bool isPerpetual = false; - uint256 priceWad = AssemblyLib.scaleToWad(2000 * 10 ** 6, 6); - - (bytes memory strategyData, uint256 initialX, uint256 initialY) = - INormalStrategy(subject().DEFAULT_STRATEGY()).getStrategyData( - strikePriceWad, - volatilityBasisPoints, - durationSeconds, - isPerpetual, - priceWad - ); - - uint64 poolId = subject().createPool( - pairId, - initialX, - initialY, - 200, - 100, - address(this), - subject().DEFAULT_STRATEGY(), - strategyData - ); - - uint256 amount0 = 10 ether; - uint256 amount1 = 20_000 * 10 ** 6; - - MockERC20(asset).mint(address(this), 100 ether); - MockERC20(asset).approve(address(subject()), 100 ether); - MockERC20(quote).mint(address(this), 200_000 * 10 ** 6); - MockERC20(quote).approve(address(subject()), 200_000 * 10 ** 6); - - uint128 deltaLiquidity = - subject().getMaxLiquidity(poolId, amount0, amount1); - - console.log(deltaLiquidity); - - (uint128 deltaAsset, uint128 deltaQuote) = - subject().getLiquidityDeltas(poolId, int128(deltaLiquidity)); - - (uint256 usedDeltaAsset, uint256 usedDeltaQuote) = subject().allocate( - false, - address(this), - poolId, - deltaLiquidity, - uint128(amount0), - uint128(amount1) - ); - - console.log(deltaAsset); - console.log(deltaQuote); - console.log("Used:", usedDeltaAsset); - console.log("Used:", usedDeltaQuote); - - string memory uri = ERC1155(address(subject())).uri(poolId); - console.log(uri); - } - - function test_balanceOf_allocating_sets_balance() - public - defaultConfig - useActor - usePairTokens(10 ether) - { - uint128 liquidity = 1 ether; - - subject().allocate( - false, - address(this), - ghost().poolId, - liquidity, - type(uint128).max, - type(uint128).max - ); - - assertEq( - ERC1155(address(subject())).balanceOf(address(this), ghost().poolId), - liquidity - BURNED_LIQUIDITY - ); - } - */ - - function test_balanceOf_allocating_increases_balance() - public - defaultConfig - useActor - usePairTokens(10 ether) - { - uint128 liquidity = 1 ether; - - subject().allocate( - false, - address(this), - ghost().poolId, - liquidity, - type(uint128).max, - type(uint128).max - ); - - subject().allocate( - false, - address(this), - ghost().poolId, - liquidity, - type(uint128).max, - type(uint128).max - ); - - assertEq( - ERC1155(address(subject())).balanceOf(address(this), ghost().poolId), - liquidity * 2 - BURNED_LIQUIDITY - ); - } - - function test_balanceOf_deallocating_reduces_balance() - public - defaultConfig - useActor - usePairTokens(10 ether) - { - uint128 liquidity = 1 ether; - - subject().allocate( - false, - address(this), - ghost().poolId, - liquidity, - type(uint128).max, - type(uint128).max - ); - - subject().deallocate( - false, ghost().poolId, uint128(liquidity - BURNED_LIQUIDITY), 0, 0 - ); - - assertEq( - ERC1155(address(subject())).balanceOf(address(this), ghost().poolId), - 0 - ); - } -} diff --git a/test/TestPositionRendererUri.t.sol b/test/TestPositionRendererUri.t.sol new file mode 100644 index 00000000..293462a4 --- /dev/null +++ b/test/TestPositionRendererUri.t.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.4; + +import "solmate/tokens/ERC1155.sol"; +import "../contracts/libraries/AssemblyLib.sol"; +import "./Setup.sol"; +import "../contracts/strategies/NormalStrategy.sol"; + +contract TestPositionRendererUri is Setup { + struct MetaContext { + address asset; + address quote; + uint256 strikePriceWad; + uint256 durationSeconds; + uint16 swapFee; + bool isPerpetual; + uint256 priceWad; + address controller; + address strategy; + uint16 prioritySwapFee; + } + + function _createPool(MetaContext memory ctx) + public + returns (uint64 poolId) + { + uint24 pairId = subject().createPair(ctx.asset, ctx.quote); + + (bytes memory strategyData, uint256 initialX, uint256 initialY) = + INormalStrategy(subject().DEFAULT_STRATEGY()).getStrategyData( + ctx.strikePriceWad, + 1_00, // volatilityBasisPoints + ctx.durationSeconds, + ctx.isPerpetual, + ctx.priceWad + ); + + poolId = subject().createPool( + pairId, + initialX, + initialY, + ctx.swapFee, + ctx.prioritySwapFee, + ctx.controller, + ctx.strategy, + strategyData + ); + } + + function _allocate( + MetaContext memory ctx, + uint64 poolId, + uint256 amount0, + uint256 amount1 + ) public { + MockERC20(ctx.asset).mint(address(this), amount0); + MockERC20(ctx.asset).approve(address(subject()), amount0); + MockERC20(ctx.quote).mint(address(this), amount1); + MockERC20(ctx.quote).approve(address(subject()), amount1); + + uint128 deltaLiquidity = + subject().getMaxLiquidity(poolId, amount0, amount1); + + (uint128 deltaAsset, uint128 deltaQuote) = + subject().getLiquidityDeltas(poolId, int128(deltaLiquidity)); + + subject().allocate( + false, address(this), poolId, deltaLiquidity, deltaAsset, deltaQuote + ); + } + + function test_uri() public { + MetaContext memory ctx = MetaContext({ + asset: address(new MockERC20("Ethereum", "ETH", 18)), + quote: address(new MockERC20("USD Coin", "USDC", 6)), + strikePriceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + durationSeconds: 86_400 * 3, + swapFee: 400, + isPerpetual: false, + priceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + controller: address(0), + strategy: address(0), + prioritySwapFee: 0 + }); + + uint64 poolId = _createPool(ctx); + _allocate(ctx, poolId, 1 ether, 2_000 * 10 ** 6); + console.log(ERC1155(address(subject())).uri(poolId)); + } + + function test_uri_controlled_pool() public { + MetaContext memory ctx = MetaContext({ + asset: address(new MockERC20("Ethereum", "ETH", 18)), + quote: address(new MockERC20("USD Coin", "USDC", 6)), + strikePriceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + durationSeconds: 86_400 * 3, + swapFee: 400, + isPerpetual: false, + priceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + controller: address(this), + strategy: address(0), + prioritySwapFee: 200 + }); + + uint64 poolId = _createPool(ctx); + _allocate(ctx, poolId, 1 ether, 2_000 * 10 ** 6); + console.log(ERC1155(address(subject())).uri(poolId)); + } + + function test_uri_perpetual() public { + MetaContext memory ctx = MetaContext({ + asset: address(new MockERC20("Ethereum", "ETH", 18)), + quote: address(new MockERC20("USD Coin", "USDC", 6)), + strikePriceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + durationSeconds: 86_400 * 3, + swapFee: 400, + isPerpetual: true, + priceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + controller: address(0), + strategy: address(0), + prioritySwapFee: 0 + }); + + uint64 poolId = _createPool(ctx); + _allocate(ctx, poolId, 1 ether, 2_000 * 10 ** 6); + console.log(ERC1155(address(subject())).uri(poolId)); + } + + function test_uri_custom_strategy() public { + MetaContext memory ctx = MetaContext({ + asset: address(new MockERC20("Ethereum", "ETH", 18)), + quote: address(new MockERC20("USD Coin", "USDC", 6)), + strikePriceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + durationSeconds: 86_400 * 3, + swapFee: 400, + isPerpetual: false, + priceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + controller: address(0), + strategy: address(new NormalStrategy(address(subject()))), + prioritySwapFee: 0 + }); + + uint64 poolId = _createPool(ctx); + _allocate(ctx, poolId, 1 ether, 2_000 * 10 ** 6); + console.log(ERC1155(address(subject())).uri(poolId)); + } + + function test_uri_controlled_custom_strategy() public { + MetaContext memory ctx = MetaContext({ + asset: address(new MockERC20("Ethereum", "ETH", 18)), + quote: address(new MockERC20("USD Coin", "USDC", 6)), + strikePriceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + durationSeconds: 86_400 * 3, + swapFee: 400, + isPerpetual: false, + priceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + controller: address(this), + strategy: address(new NormalStrategy(address(subject()))), + prioritySwapFee: 100 + }); + + uint64 poolId = _createPool(ctx); + _allocate(ctx, poolId, 1 ether, 2_000 * 10 ** 6); + console.log(ERC1155(address(subject())).uri(poolId)); + } +} From a1e71a0569cc5a041249b080fd124ee2c427e799 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 1 Sep 2023 18:04:21 +0400 Subject: [PATCH 31/48] test: rename test file --- ...PortfolioBalanceOf.t copy.sol => TestPortfolioBalanceOf.t.sol} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{TestPortfolioBalanceOf.t copy.sol => TestPortfolioBalanceOf.t.sol} (100%) diff --git a/test/TestPortfolioBalanceOf.t copy.sol b/test/TestPortfolioBalanceOf.t.sol similarity index 100% rename from test/TestPortfolioBalanceOf.t copy.sol rename to test/TestPortfolioBalanceOf.t.sol From 7c5047a5a888dead83e0a56b33e70e9b68353d54 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 1 Sep 2023 18:14:40 +0400 Subject: [PATCH 32/48] test: update _createPool func util in uri tests --- test/TestPositionRendererUri.t.sol | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/TestPositionRendererUri.t.sol b/test/TestPositionRendererUri.t.sol index 293462a4..96bf90fb 100644 --- a/test/TestPositionRendererUri.t.sol +++ b/test/TestPositionRendererUri.t.sol @@ -24,7 +24,11 @@ contract TestPositionRendererUri is Setup { public returns (uint64 poolId) { - uint24 pairId = subject().createPair(ctx.asset, ctx.quote); + uint24 pairId = subject().getPairId(ctx.asset, ctx.quote); + + if (pairId == 0) { + pairId = subject().createPair(ctx.asset, ctx.quote); + } (bytes memory strategyData, uint256 initialX, uint256 initialY) = INormalStrategy(subject().DEFAULT_STRATEGY()).getStrategyData( From 1f3d1c422312691c3150b7a2e58739061d4b364d Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 1 Sep 2023 18:36:21 +0400 Subject: [PATCH 33/48] feat: add PortfolioRegistry --- contracts/PortfolioRegistry.sol | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 contracts/PortfolioRegistry.sol diff --git a/contracts/PortfolioRegistry.sol b/contracts/PortfolioRegistry.sol new file mode 100644 index 00000000..d561ae6a --- /dev/null +++ b/contracts/PortfolioRegistry.sol @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.4; + +import "./interfaces/IERC20.sol"; +import "solmate/auth/Owned.sol"; + +interface PortfolioLike { + function setProtocolFee(uint256) external; + function claimFee(address, uint256) external; +} + +interface IPortfolioRegistry { + function controller() external view returns (address); +} + +/// @dev Basic registry with a single owner. +contract PortfolioRegistry is IPortfolioRegistry, Owned { + constructor(address owner_) Owned(owner_) { } + + function controller() external view returns (address) { + return owner; + } + + function setFee(address portfolio, uint256 fee) external onlyOwner { + PortfolioLike(portfolio).setProtocolFee(fee); + } + + function claimFee( + address portfolio, + address token, + uint256 amount + ) public onlyOwner { + PortfolioLike(portfolio).claimFee(token, amount); + } + + function withdraw(address token, uint256 amount) external onlyOwner { + require(amount > 0, "SimpleRegistry/invalid-amount"); + require( + IERC20(token).transfer(msg.sender, amount), + "SimpleRegistry/transfer-failed" + ); + } +} From 2a37c0cbcf454cfa378c68d7cc9c3ebae84cacec Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 1 Sep 2023 18:38:03 +0400 Subject: [PATCH 34/48] test: add TestPortfolioRegistry --- test/TestPortfolioRegistry.t.sol | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 test/TestPortfolioRegistry.t.sol diff --git a/test/TestPortfolioRegistry.t.sol b/test/TestPortfolioRegistry.t.sol new file mode 100644 index 00000000..25d7a544 --- /dev/null +++ b/test/TestPortfolioRegistry.t.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.4; + +import "forge-std/Test.sol"; +import "../contracts/PortfolioRegistry.sol"; + +contract TestPortfolioRegistry is Test { + PortfolioRegistry public registry; + + function test_controller() public { + registry = new PortfolioRegistry(address(this)); + assertEq(registry.controller(), address(this)); + } +} From aa3d34b1587495764b6f17caee8714647a2e9308 Mon Sep 17 00:00:00 2001 From: clemlak Date: Fri, 1 Sep 2023 18:40:43 +0400 Subject: [PATCH 35/48] chore: update Prepare script --- scripts/Prepare.s.sol | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/scripts/Prepare.s.sol b/scripts/Prepare.s.sol index 5105e1d6..3232d68d 100644 --- a/scripts/Prepare.s.sol +++ b/scripts/Prepare.s.sol @@ -5,7 +5,7 @@ import "forge-std/Script.sol"; import "solmate/tokens/WETH.sol"; import "nugu/NuguFactory.sol"; -import "../contracts/test/SimpleRegistry.sol"; +import "../contracts/PortfolioRegistry.sol"; import "../contracts/Portfolio.sol"; import "../contracts/PositionRenderer.sol"; @@ -42,7 +42,14 @@ contract Deploy is Script { function run() external { bytes memory registryDeployData = abi.encodeCall( NuguFactory.deploy, - (REGISTRY_SALT, type(SimpleRegistry).creationCode, 0) + ( + REGISTRY_SALT, + abi.encodePacked( + type(PortfolioRegistry).creationCode, + abi.encode(SAFE_ADDRESS) + ), + 0 + ) ); bytes memory positionRendererDeployData = abi.encodeCall( From 68a7ac6f827768e25018a30d1e86dd4ce2e5e3f1 Mon Sep 17 00:00:00 2001 From: clemlak Date: Mon, 4 Sep 2023 16:24:38 +0400 Subject: [PATCH 36/48] chore: add NatSpec --- contracts/PositionRenderer.sol | 166 +++++++++++++++++++++++---------- 1 file changed, 119 insertions(+), 47 deletions(-) diff --git a/contracts/PositionRenderer.sol b/contracts/PositionRenderer.sol index 6a9e382d..b1c371d6 100644 --- a/contracts/PositionRenderer.sol +++ b/contracts/PositionRenderer.sol @@ -3,18 +3,26 @@ pragma solidity ^0.8.4; import "openzeppelin/utils/Base64.sol"; import "solmate/tokens/ERC20.sol"; -import "solmate/utils/SafeCastLib.sol"; import "./libraries/StringsLib.sol"; import "./libraries/AssemblyLib.sol"; import "./interfaces/IPortfolio.sol"; -import "./interfaces/IStrategy.sol"; import "./strategies/NormalStrategy.sol"; -/// @dev Contract to render a position. +/** + * @title + * PositionRenderer + * + * @author + * Primitive™ + * + * @dev + * Prepares the metadata and generates the visual representation of the + * liquidity pool tokens. + * This contract is not meant to be called directly. + */ contract PositionRenderer { using StringsLib for *; - using SafeCastLib for *; struct Pair { address asset; @@ -61,6 +69,12 @@ contract PositionRenderer { string private constant STYLE_1 = ");animation:r 10s linear infinite;background-size:200% 200%;will-change:background-position;width:100vw;height:100vh;position:absolute;top:0;left:0;z-index:-2}#n{height:100vh;width:100vw;position:absolute;top:0;right:0;z-index:-1}@keyframes r{0%,100%{background-position:left top}50%{background-position:right bottom}}#t{font-size:6vh}.s{border-spacing:0 1rem}.s td{font-size:5vh}#i{height:15vh}.l{font-size:3.25vh;opacity:.5}.f{background-color:#00000020;padding:1rem;border-radius:8px}.f p{font-size:3vh;margin:0}"; + /** + * @dev Returns the metadata of the required liquidity pool token, following + * the ERC-1155 standard. + * @param id Id of the required pool. + * @return Minified Base64-encoded JSON containing the metadata. + */ function uri(uint256 id) external view returns (string memory) { Properties memory properties = _getProperties(id); @@ -90,6 +104,10 @@ contract PositionRenderer { ); } + /** + * @dev Returns the data associated with the asset / quote pair. + * @param id Id of the required pool. + */ function _getPair(uint256 id) internal view returns (Pair memory) { ( address tokenAsset, @@ -110,6 +128,10 @@ contract PositionRenderer { }); } + /** + * @dev Returns the data associated with the current pool. + * @param id Id of the required pool. + */ function _getPool(uint256 id) internal view returns (Pool memory) { ( uint128 virtualX, @@ -138,6 +160,10 @@ contract PositionRenderer { }); } + /** + * @dev Returns the data associated with the current pool config. + * @param id Id of the required pool. + */ function _getConfig( uint256 id, address strategy @@ -159,6 +185,11 @@ contract PositionRenderer { }); } + /** + * @dev Returns all data associated with the current pool packed within a + * struct. + * @param id Id of the required pool. + */ function _getProperties(uint256 id) private view @@ -171,6 +202,9 @@ contract PositionRenderer { return Properties({ pair: pair, pool: pool, config: config }); } + /** + * @dev Generates the name of the NFT. + */ function _generateName(Properties memory properties) private pure @@ -184,6 +218,9 @@ contract PositionRenderer { ); } + /** + * @dev Outputs all the data associated with the current pair in JSON format. + */ function _generatePair(Properties memory properties) private pure @@ -211,6 +248,9 @@ contract PositionRenderer { ); } + /** + * @dev Outputs all the data associated with the current pool in JSON format. + */ function _generatePool(Properties memory properties) private pure @@ -241,6 +281,10 @@ contract PositionRenderer { ); } + /** + * @dev Outputs all the data associated with the current pool config in JSON + * format. + */ function _generateConfig(Properties memory properties) private pure @@ -264,6 +308,9 @@ contract PositionRenderer { ); } + /** + * @dev Generates the visual representation of the NFT in HTML. + */ function _generateHTML(Properties memory properties) private view @@ -298,11 +345,18 @@ contract PositionRenderer { '
', '', _generateStats(properties), - _generateHTMLFooter(properties), + _generateFooter(properties), "" ); } + /** + * @dev Generates a
" ); } + /** + * @dev Generates the stats
", PRIMITIVE_LOGO, "', label, "
", amount, "
', + label, + "
", + " ", + symbol, + "
', + label, + "
", + " %
element representing a stat. + * @param label Name of the stat. + * @param amount Full amount (including the decimals). + * @param decimals Decimals of the token. + * @param symbol Ticker of the token. + */ function _generateStat( string memory label, uint256 amount, @@ -323,6 +377,11 @@ contract PositionRenderer { ); } + /** + * @dev Generates a element representing a percentage stat. + * @param label Name of the stat. + * @param amount Full amount (using a 10,000 base). + */ function _generatePercentStat( string memory label, uint256 amount @@ -337,19 +396,33 @@ contract PositionRenderer { ); } - function _generateTitle( - string memory label, - string memory amount - ) private pure returns (string memory) { + /** + * @dev Generates a element containing the title. + */ + function _generateTitle(Properties memory properties) + private + view + returns (string memory) + { return string.concat( '', - label, + string.concat( + properties.pair.assetSymbol, "-", properties.pair.quoteSymbol + ), '
', - amount, + properties.config.isPerpetual + ? "Perpetual pool" + : ( + properties.config.creationTimestamp + + properties.config.durationSeconds + ).toCountdown(), "
element. + */ function _generateStats(Properties memory properties) private view @@ -360,43 +433,24 @@ contract PositionRenderer { "", - _generateHTMLTitle(properties), + _generateTitle(properties), "", - _generateHTMLSpotPrice(properties), - _generateHTMLStrikePrice(properties), + _generateSpotPrice(properties), + _generateStrikePrice(properties), "", - _generateHTMLAssetReserves(properties), - _generateHTMLQuoteReserves(properties), + _generateAssetReserves(properties), + _generateQuoteReserves(properties), "", - _generateHTMLPoolValuation(properties), - _generateHTMLSwapFee(properties), + _generatePoolValuation(properties), + _generateSwapFee(properties), "
", PRIMITIVE_LOGO, "
" ); } - function _generateHTMLTitle(Properties memory properties) - internal - view - returns (string memory) - { - return string.concat( - _generateTitle( - string.concat( - properties.pair.assetSymbol, - "-", - properties.pair.quoteSymbol - ), - properties.config.isPerpetual - ? "Perpetual pool" - : ( - properties.config.creationTimestamp - + properties.config.durationSeconds - ).toCountdown() - ) - ); - } - - function _generateHTMLSpotPrice(Properties memory properties) + /** + * @dev Generates the spot price element. + */ + function _generateSpotPrice(Properties memory properties) internal pure returns (string memory) @@ -413,7 +467,10 @@ contract PositionRenderer { ); } - function _generateHTMLStrikePrice(Properties memory properties) + /** + * @dev Generates the strike price element. + */ + function _generateStrikePrice(Properties memory properties) internal pure returns (string memory) @@ -431,7 +488,10 @@ contract PositionRenderer { ); } - function _generateHTMLAssetReserves(Properties memory properties) + /** + * @dev Calculates the asset reserves and generates the element. + */ + function _generateAssetReserves(Properties memory properties) internal pure returns (string memory) @@ -448,7 +508,10 @@ contract PositionRenderer { ); } - function _generateHTMLQuoteReserves(Properties memory properties) + /** + * @dev Calculates the quote reserves and generates the element. + */ + function _generateQuoteReserves(Properties memory properties) internal pure returns (string memory) @@ -465,7 +528,10 @@ contract PositionRenderer { ); } - function _generateHTMLPoolValuation(Properties memory properties) + /** + * @dev Calculates the pool valuation and generates the element. + */ + function _generatePoolValuation(Properties memory properties) internal pure returns (string memory) @@ -490,7 +556,10 @@ contract PositionRenderer { ); } - function _generateHTMLSwapFee(Properties memory properties) + /** + * @dev Generates the swap fee element. + */ + function _generateSwapFee(Properties memory properties) internal pure returns (string memory) @@ -500,7 +569,10 @@ contract PositionRenderer { ); } - function _generateHTMLFooter(Properties memory properties) + /** + * @dev Generates the footer
element. + */ + function _generateFooter(Properties memory properties) internal pure returns (string memory) From a9c574d8061b027864827b8548eccb99ec0bdc9b Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 5 Sep 2023 09:49:13 +0400 Subject: [PATCH 37/48] fix: pair details, color generation --- contracts/PositionRenderer.sol | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/contracts/PositionRenderer.sol b/contracts/PositionRenderer.sol index b1c371d6..ec0e1674 100644 --- a/contracts/PositionRenderer.sol +++ b/contracts/PositionRenderer.sol @@ -6,6 +6,7 @@ import "solmate/tokens/ERC20.sol"; import "./libraries/StringsLib.sol"; import "./libraries/AssemblyLib.sol"; +import { PoolIdLib } from "./libraries/PoolLib.sol"; import "./interfaces/IPortfolio.sol"; import "./strategies/NormalStrategy.sol"; @@ -106,7 +107,7 @@ contract PositionRenderer { /** * @dev Returns the data associated with the asset / quote pair. - * @param id Id of the required pool. + * @param id Id of the pair associated with the required pool. */ function _getPair(uint256 id) internal view returns (Pair memory) { ( @@ -114,7 +115,9 @@ contract PositionRenderer { uint8 decimalsAsset, address tokenQuote, uint8 decimalsQuote - ) = IPortfolio(msg.sender).pairs(uint24(id)); + ) = IPortfolio(msg.sender).pairs( + uint24(PoolIdLib.pairId(PoolId.wrap(uint64(id)))) + ); return Pair({ asset: tokenAsset, @@ -318,11 +321,17 @@ contract PositionRenderer { { string memory color0 = StringsLib.toHexColor( bytes3( - keccak256(abi.encode((properties.pool.poolId >> 232) << 232)) + keccak256( + abi.encode(properties.pool.poolId, properties.pair.asset) + ) ) ); string memory color1 = StringsLib.toHexColor( - bytes3(keccak256(abi.encode(properties.pool.poolId << 232))) + bytes3( + keccak256( + abi.encode(properties.pool.poolId, properties.pair.quote) + ) + ) ); string memory title = string.concat( From 6ed81d1ee1d70457a2caf496875b4d06e29849ec Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 5 Sep 2023 09:49:23 +0400 Subject: [PATCH 38/48] test: add test_uri_many_pools --- test/TestPositionRendererUri.t.sol | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/TestPositionRendererUri.t.sol b/test/TestPositionRendererUri.t.sol index 96bf90fb..cf3346d7 100644 --- a/test/TestPositionRendererUri.t.sol +++ b/test/TestPositionRendererUri.t.sol @@ -167,4 +167,25 @@ contract TestPositionRendererUri is Setup { _allocate(ctx, poolId, 1 ether, 2_000 * 10 ** 6); console.log(ERC1155(address(subject())).uri(poolId)); } + + function test_uri_many_pools() public { + MetaContext memory ctx = MetaContext({ + asset: address(new MockERC20("Ethereum", "ETH", 18)), + quote: address(new MockERC20("USD Coin", "USDC", 6)), + strikePriceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + durationSeconds: 86_400 * 3, + swapFee: 400, + isPerpetual: false, + priceWad: AssemblyLib.scaleToWad(1666 * 10 ** 6, 6), + controller: address(0), + strategy: address(0), + prioritySwapFee: 0 + }); + + _createPool(ctx); + _createPool(ctx); + uint64 poolId = _createPool(ctx); + _allocate(ctx, poolId, 1 ether, 2_000 * 10 ** 6); + console.log(ERC1155(address(subject())).uri(poolId)); + } } From d0569aaae9ee457e862b366790d07cd4c3644888 Mon Sep 17 00:00:00 2001 From: clemlak Date: Tue, 5 Sep 2023 14:43:27 +0400 Subject: [PATCH 39/48] chore: add test script to generate data --- scripts/TestDeploy.s.sol | 121 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 scripts/TestDeploy.s.sol diff --git a/scripts/TestDeploy.s.sol b/scripts/TestDeploy.s.sol new file mode 100644 index 00000000..67a946c6 --- /dev/null +++ b/scripts/TestDeploy.s.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; +import "solmate/test/utils/mocks/MockERC20.sol"; + +import "../contracts/test/SimpleRegistry.sol"; +import "../contracts/Portfolio.sol"; +import "../contracts/PositionRenderer.sol"; +import "../contracts/libraries/AssemblyLib.sol"; + +contract TestDeploy is Script { + function run() external { + uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); + address sender = vm.addr(deployerPrivateKey); + console.log("Sender:", sender); + vm.startBroadcast(deployerPrivateKey); + + MockERC20 usdt = new MockERC20("USDT", "USDT", 18); + MockERC20 dai = new MockERC20("DAI", "DAI", 18); + MockERC20 weth = new MockERC20("Wrapped Ether", "WETH", 18); + MockERC20 usdc = new MockERC20("USD Coin", "USDC", 6); + + address registry = address(new SimpleRegistry()); + address positionRenderer = address(new PositionRenderer()); + Portfolio portfolio = + new Portfolio(address(weth), registry, positionRenderer); + + // USDT - DAI + + uint64 poolId = _createPool( + portfolio, + address(usdt), + address(dai), + AssemblyLib.scaleToWad(1 ether, 18) + ); + _allocate(sender, portfolio, poolId, usdt, dai, 1000 ether, 1000 ether); + + // USDT - USDC + + poolId = _createPool( + portfolio, + address(usdt), + address(usdc), + AssemblyLib.scaleToWad(1 * 10 ** 6, 6) + ); + _allocate( + sender, portfolio, poolId, usdt, usdc, 2000 ether, 2000 * 10 ** 6 + ); + + // WETH - USDC + poolId = _createPool( + portfolio, + address(weth), + address(usdc), + AssemblyLib.scaleToWad(2000 * 10 ** 6, 6) + ); + _allocate( + sender, portfolio, poolId, weth, usdc, 2 ether, 4000 * 10 ** 6 + ); + + console.log(unicode"🚀 Contracts deployed!"); + console.log("Portfolio:", address(portfolio)); + + vm.stopBroadcast(); + } + + function _createPool( + Portfolio portfolio, + address asset, + address quote, + uint256 price + ) public returns (uint64) { + uint24 pairId = portfolio.getPairId(asset, quote); + + if (pairId == 0) pairId = portfolio.createPair(asset, quote); + + (bytes memory strategyData, uint256 initialX, uint256 initialY) = + INormalStrategy(portfolio.DEFAULT_STRATEGY()).getStrategyData( + price, 1_00, 3 days, false, price + ); + + uint64 poolId = portfolio.createPool( + pairId, + initialX, + initialY, + 100, + 0, + address(0), + address(0), + strategyData + ); + + return poolId; + } + + function _allocate( + address sender, + Portfolio portfolio, + uint64 poolId, + MockERC20 asset, + MockERC20 quote, + uint128 amount0, + uint128 amount1 + ) public { + asset.mint(sender, amount0); + asset.approve(address(portfolio), amount0); + quote.mint(sender, amount1); + quote.approve(address(portfolio), amount1); + + uint128 deltaLiquidity = + portfolio.getMaxLiquidity(poolId, amount0, amount1); + + (uint128 deltaAsset, uint128 deltaQuote) = + portfolio.getLiquidityDeltas(poolId, int128(deltaLiquidity)); + + portfolio.allocate( + false, sender, poolId, deltaLiquidity, deltaAsset, deltaQuote + ); + } +} From 3e6a4ec7cc35941cf34168f5159c06cbf5800171 Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 7 Sep 2023 12:58:40 +0400 Subject: [PATCH 40/48] feat: delete duplicate IPortfolioRegistry --- contracts/PortfolioRegistry.sol | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/contracts/PortfolioRegistry.sol b/contracts/PortfolioRegistry.sol index d561ae6a..34cca726 100644 --- a/contracts/PortfolioRegistry.sol +++ b/contracts/PortfolioRegistry.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.4; import "./interfaces/IERC20.sol"; +import "./interfaces/IPortfolioRegistry.sol"; import "solmate/auth/Owned.sol"; interface PortfolioLike { @@ -9,10 +10,6 @@ interface PortfolioLike { function claimFee(address, uint256) external; } -interface IPortfolioRegistry { - function controller() external view returns (address); -} - /// @dev Basic registry with a single owner. contract PortfolioRegistry is IPortfolioRegistry, Owned { constructor(address owner_) Owned(owner_) { } From 6bb33bf08c398129b34ac0f8c8be60afc517ec20 Mon Sep 17 00:00:00 2001 From: clemlak Date: Thu, 7 Sep 2023 12:58:52 +0400 Subject: [PATCH 41/48] chore: update Prepare script --- scripts/Prepare.s.sol | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/scripts/Prepare.s.sol b/scripts/Prepare.s.sol index 3232d68d..1920ee32 100644 --- a/scripts/Prepare.s.sol +++ b/scripts/Prepare.s.sol @@ -9,13 +9,15 @@ import "../contracts/PortfolioRegistry.sol"; import "../contracts/Portfolio.sol"; import "../contracts/PositionRenderer.sol"; -// This script prepares all the transaction data that can be used to deploy +// This script prepares all the transaction data that must be used to deploy // Portfolio and its dependency contracts. // // Here are the steps to use it: // 1. Be sure to update all the constants below: contract addresses and salts. // 2. Run the script with: // `forge script ./scripts/Prepare.s.sol --via-ir --optimizer-runs 0`. +// +// If you want to deploy from a Safe Wallet, you can follow the steps below: // 3. Visit https://app.safe.global/, click on your multisig wallet, then on // "New transaction" and finally "Transaction builder". // 4. Paste the address of the Nugu factory and enable the "custom data". @@ -25,9 +27,16 @@ import "../contracts/PositionRenderer.sol"; // 6. After that, a total of 3 transactions must compose the batch. You can then // hit "Create Batch", and either "Simulate" or directly "Send Batch". // 7. Last step is simply to follow the instructions to send the transaction. - -address constant SAFE_ADDRESS = 0x8cDb0095ceddb092aD592aCE2971e4f364b5E8eE; -address constant NUGU_ADDRESS = 0xe95671B131FD0f401BA6a68F55A79542E27C43De; +// +// If you want to deploy from a hardware wallet, you can follow the steps below: +// 3. Visit any website allowing you to send a transaction with custom data (such +// as https://www.myetherwallet.com/). +// 4. Paste the address of the Nugu factory as a recipient, paste the first custom +// data that was generated and send the transaction. +// 5. Repeat step 4 for all the data generated by this script (3 in total). + +address constant MULTISIG_WALLET = 0x8cDb0095ceddb092aD592aCE2971e4f364b5E8eE; +address constant NUGU_ADDRESS = 0xe50ea0e9849cb17829907Acaf764af8F37d4938E; address constant WETH_ADDRESS = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6; bytes32 constant REGISTRY_SALT = keccak256("REGISTRY0"); @@ -46,7 +55,7 @@ contract Deploy is Script { REGISTRY_SALT, abi.encodePacked( type(PortfolioRegistry).creationCode, - abi.encode(SAFE_ADDRESS) + abi.encode(MULTISIG_WALLET) ), 0 ) @@ -58,10 +67,10 @@ contract Deploy is Script { ); address predictedRegistryAddress = - getDeployed(NUGU_ADDRESS, SAFE_ADDRESS, REGISTRY_SALT); + getDeployed(NUGU_ADDRESS, MULTISIG_WALLET, REGISTRY_SALT); address predictedPositionRendererAddress = - getDeployed(NUGU_ADDRESS, SAFE_ADDRESS, POSITION_RENDERER_SALT); + getDeployed(NUGU_ADDRESS, MULTISIG_WALLET, POSITION_RENDERER_SALT); bytes memory portfolioDeployData = abi.encodeCall( NuguFactory.deploy, From f63c81e8ebb799866e8bd073ed841989fb32bc40 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 9 Aug 2023 15:11:23 -0700 Subject: [PATCH 42/48] docs(readme): updates readme with existing addresses --- README.md | 102 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 79 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 62a036ca..3d94b67b 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,62 @@ -> Beta: Not production ready. Pending on-going audits. +> Beta: Portfolio is experimental software. Use at your own risk. # Portfolio by Primitive +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/primitivefinance/portfolio#contributing) [![](https://dcbadge.vercel.app/api/server/primitive?style=flat)](https://discord.gg/primitive) [![Twitter Badge](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/primitivefi) Portfolio is an automated market making protocol for implementing custom liquidity distribution strategies at the lowest cost possible. -[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/primitivefinance/portfolio#contributing) [![](https://dcbadge.vercel.app/api/server/primitive?style=flat)](https://discord.gg/primitive) [![Twitter Badge](https://badgen.net/badge/icon/twitter?icon=twitter&label)](https://twitter.com/primitivefi) +## Table of Contents + +- [Overview](#overview) +- [Deployments](#deployments) +- [Security](#security) +- [Install](#install) +- [Documentation](#documentation) +- [Resources](#resources) +- [Contributing](#contributing) +- [License](#license) + +## Overview + +Portfolio is an automated market making protocol for creating custom liquidity distribution strategies at the lowest cost possible. Each pool in Portfolio can be created with a default strategy or custom strategy that defines a trading function, determining the available prices offered by the provider's liquidity. These pools all exist within the single Portfolio smart contract resulting in significantly lower gas costs for liquidity providers and swappers. + +Read the local [docs](./docs/src/), hosted docs [docs.primitive.xyz](https://docs.primitive.xyz), or the [formal specification](https://primitive.xyz/whitepaper) for more information. -## Installation + +## Deployments + +### Canonical Cross-chain Deployment Addresses + +| Contract | Canonical cross-chain address | +| -------------------- | -------------------------------------------- | +| Portfolio 1.3.0-beta | `0x82360b9a2076a09ea8abe2b3e11aed89de3a02d1` | +| Portfolio 1.4.0-beta | `todo` | + +### Deployments by Chain + +| Network | Portfolio 1.3.0-beta | Portfolio v1.4.0-beta | +| -------- | ----------------------------------------------------------------------------------------------------------------------------- | --------------------- | +| Ethereum | [0x82360b9a2076a09ea8abe2b3e11aed89de3a02d1](https://etherscan.io/address/0x82360b9a2076a09ea8abe2b3e11aed89de3a02d1 ) | todo | +| Base | n/a | todo | +| Sepolia | [0x82360b9a2076a09ea8abe2b3e11aed89de3a02d1](https://sepolia.etherscan.io/address/0x82360b9a2076a09ea8abe2b3e11aed89de3a02d1) | todo | + +# Security + +[Visit Primitive Security](https://www.primitive.xyz/security) to view a comprehensive overview of the security initiatives of Portfolio. + +## Audits + +| Security Firm | Date | Review Time | Status | Final Commit w/ Fixes | +| --------------------- | ---------- | ----------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| ChainSecurity | 2022-05-31 | 8-weeks | Completed | [c6f692f41c1d20ac09acb832923bd46500fd8e06](https://github.com/primitivefinance/portfolio/commit/c6f692f41c1d20ac09acb832923bd46500fd8e06) | +| Trail of Bits | 2023-01-31 | 8-weeks | Completed | n/a | +| Spearbit #1 | 2023-03-31 | 5-weeks | Completed | [tag/v1.1.0-beta](https://github.com/primitivefinance/portfolio/releases/tag/v1.1.0-beta) | +| Spearbit #1 Extension | 2023-05-12 | 2-weeks | Competed | [36e9efa28332fa03f6d5910edda2fec2f5937190](https://github.com/primitivefinance/portfolio/commit/36e9efa28332fa03f6d5910edda2fec2f5937190 ) | +| Spearbit #2 | 2023-07-78 | 2-weeks | Completed | [tag/v1.4.0-beta-spearbit-2023-08-complete](https://github.com/primitivefinance/portfolio/releases/tag/v1.4.0-beta-spearbit-2023-08-complete) | + +## Install + +To install locally and compile contracts: #### [Required] Foundry. [Source](https://github.com/foundry-rs/foundry). If not installed, run the following: @@ -17,26 +67,35 @@ curl -L https://foundry.paradigm.xyz | bash # Restart terminal or reload `PATH`, then run foundryup foundryup ``` +#### [Required] Install Deps +```bash +forge install +``` -### Install & Run +#### Usage +##### Testing ```bash -forge install & forge test +forge test ``` -# Security +##### Coverage -[Visit Primitive Security](https://www.primitive.xyz/security) to view a comprehensive overview of the security initiatives of Portfolio. +[Optional] Install coverage gutters [vs code extension](https://github.com/ryanluker/vscode-coverage-gutters). -## Audits +Then run this to generate a coverage report: -| Security Firm | Review Time | Status | -| ------------------ | ----------- | --------- | -| ChainSecurity | 8-weeks | Completed | -| Trail of Bits | 8-weeks | Completed | -| Spearbit | 5-weeks | Completed | -| Spearbit Extension | 2-weeks | Competed | -| Spearbit Extension #2 | 2-weeks | Pending | +```bash +forge coverage --report lcov +``` + +### Install Artifacts via NPM +To install the artifacts to use in your own project: + + +```bash +npm install @primitivexyz/portfolio +``` # Documentation @@ -73,15 +132,7 @@ cd docs mdbook serve --open ``` -## coverage - -[Optional] Install coverage gutters [vs code extension](https://github.com/ryanluker/vscode-coverage-gutters). -Then run this to generate a coverage report: - -```bash -forge coverage --report lcov -``` ## Resources @@ -113,3 +164,8 @@ When making a pull request: - New tests for all new features or code paths. - If making a modification to third-party dependencies, yarn audit passes. - A descriptive summary of the PR has been provided. + + +## Copyright + +[AGPL-3.0](./LICENSE) © 2023 Primitive Bits, Inc. \ No newline at end of file From e3111018443e8f8c233efe015fd2b132886abb0c Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 9 Aug 2023 15:28:59 -0700 Subject: [PATCH 43/48] chore(yarn-audit): updated vuln deps --- yarn.lock | 455 +++++++++++++++++++----------------------------------- 1 file changed, 156 insertions(+), 299 deletions(-) diff --git a/yarn.lock b/yarn.lock index f607f54a..5b7de8ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,36 +3,37 @@ "@babel/code-frame@^7.0.0": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.18.6.tgz#3b25d38c89600baa2dcc219edfa88a74eb2c427a" - integrity sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q== + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3" + integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA== dependencies: - "@babel/highlight" "^7.18.6" + "@babel/highlight" "^7.22.10" + chalk "^2.4.2" -"@babel/helper-validator-identifier@^7.18.6": - version "7.19.1" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" - integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== +"@babel/helper-validator-identifier@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193" + integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ== -"@babel/highlight@^7.18.6": - version "7.18.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf" - integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g== +"@babel/highlight@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7" + integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ== dependencies: - "@babel/helper-validator-identifier" "^7.18.6" - chalk "^2.0.0" + "@babel/helper-validator-identifier" "^7.22.5" + chalk "^2.4.2" js-tokens "^4.0.0" "@commitlint/cli@^17.4.2": - version "17.4.2" - resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-17.4.2.tgz#8600c83abb7e84191fd59528fc14f436496fb00b" - integrity sha512-0rPGJ2O1owhpxMIXL9YJ2CgPkdrFLKZElIZHXDN8L8+qWK1DGH7Q7IelBT1pchXTYTuDlqkOTdh//aTvT3bSUA== - dependencies: - "@commitlint/format" "^17.4.0" - "@commitlint/lint" "^17.4.2" - "@commitlint/load" "^17.4.2" - "@commitlint/read" "^17.4.2" - "@commitlint/types" "^17.4.0" + version "17.7.0" + resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-17.7.0.tgz#c62213abdf47764704c8dc3d2ca5f53fb1fa1371" + integrity sha512-28PNJaGuBQZNoz3sd+6uO3b4+5PY+vWzyBfy5JOvFB7QtoZVXf2FYTQs5VO1cn7yAd3y9/0Rx0x6Vx82W/zhuA== + dependencies: + "@commitlint/format" "^17.4.4" + "@commitlint/lint" "^17.7.0" + "@commitlint/load" "^17.7.0" + "@commitlint/read" "^17.5.1" + "@commitlint/types" "^17.4.4" execa "^5.0.0" lodash.isfunction "^3.0.9" resolve-from "5.0.0" @@ -46,20 +47,20 @@ dependencies: conventional-changelog-conventionalcommits "^5.0.0" -"@commitlint/config-validator@^17.4.0": - version "17.4.0" - resolved "https://registry.yarnpkg.com/@commitlint/config-validator/-/config-validator-17.4.0.tgz#2cb229672a22476cf1f21bedbfcd788e5da5b54f" - integrity sha512-Sa/+8KNpDXz4zT4bVbz2fpFjvgkPO6u2V2fP4TKgt6FjmOw2z3eEX859vtfeaTav/ukBw0/0jr+5ZTZp9zCBhA== +"@commitlint/config-validator@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/config-validator/-/config-validator-17.6.7.tgz#c664d42a1ecf5040a3bb0843845150f55734df41" + integrity sha512-vJSncmnzwMvpr3lIcm0I8YVVDJTzyjy7NZAeXbTXy+MPUdAr9pKyyg7Tx/ebOQ9kqzE6O9WT6jg2164br5UdsQ== dependencies: - "@commitlint/types" "^17.4.0" + "@commitlint/types" "^17.4.4" ajv "^8.11.0" -"@commitlint/ensure@^17.4.0": - version "17.4.0" - resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-17.4.0.tgz#3de65768bfccb9956ec3a0ecd8a415421bf315e5" - integrity sha512-7oAxt25je0jeQ/E0O/M8L3ADb1Cvweu/5lc/kYF8g/kXatI0wxGE5La52onnAUAWeWlsuvBNar15WcrmDmr5Mw== +"@commitlint/ensure@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-17.6.7.tgz#77a77a0c05e6a1c34589f59e82e6cb937101fc4b" + integrity sha512-mfDJOd1/O/eIb/h4qwXzUxkmskXDL9vNPnZ4AKYKiZALz4vHzwMxBSYtyL2mUIDeU9DRSpEUins8SeKtFkYHSw== dependencies: - "@commitlint/types" "^17.4.0" + "@commitlint/types" "^17.4.4" lodash.camelcase "^4.3.0" lodash.kebabcase "^4.1.1" lodash.snakecase "^4.1.1" @@ -71,98 +72,95 @@ resolved "https://registry.yarnpkg.com/@commitlint/execute-rule/-/execute-rule-17.4.0.tgz#4518e77958893d0a5835babe65bf87e2638f6939" integrity sha512-LIgYXuCSO5Gvtc0t9bebAMSwd68ewzmqLypqI2Kke1rqOqqDbMpYcYfoPfFlv9eyLIh4jocHWwCK5FS7z9icUA== -"@commitlint/format@^17.4.0": - version "17.4.0" - resolved "https://registry.yarnpkg.com/@commitlint/format/-/format-17.4.0.tgz#1c80cf3a6274ff9b3d3c0dd150a97882d557aa0f" - integrity sha512-Z2bWAU5+f1YZh9W76c84J8iLIWIvvm+mzqogTz0Nsc1x6EHW0Z2gI38g5HAjB0r0I3ZjR15IDEJKhsxyblcyhA== +"@commitlint/format@^17.4.4": + version "17.4.4" + resolved "https://registry.yarnpkg.com/@commitlint/format/-/format-17.4.4.tgz#0f6e1b4d7a301c7b1dfd4b6334edd97fc050b9f5" + integrity sha512-+IS7vpC4Gd/x+uyQPTAt3hXs5NxnkqAZ3aqrHd5Bx/R9skyCAWusNlNbw3InDbAK6j166D9asQM8fnmYIa+CXQ== dependencies: - "@commitlint/types" "^17.4.0" + "@commitlint/types" "^17.4.4" chalk "^4.1.0" -"@commitlint/is-ignored@^17.4.2": - version "17.4.2" - resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-17.4.2.tgz#2d40a34e071c3e595e485fafe8460457a7b7af9d" - integrity sha512-1b2Y2qJ6n7bHG9K6h8S4lBGUl6kc7mMhJN9gy1SQfUZqe92ToDjUTtgNWb6LbzR1X8Cq4SEus4VU8Z/riEa94Q== +"@commitlint/is-ignored@^17.7.0": + version "17.7.0" + resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-17.7.0.tgz#df9b284420bdb1aed5fdb2be44f4e98cc4826014" + integrity sha512-043rA7m45tyEfW7Zv2vZHF++176MLHH9h70fnPoYlB1slKBeKl8BwNIlnPg4xBdRBVNPaCqvXxWswx2GR4c9Hw== dependencies: - "@commitlint/types" "^17.4.0" - semver "7.3.8" + "@commitlint/types" "^17.4.4" + semver "7.5.4" -"@commitlint/lint@^17.4.2": - version "17.4.2" - resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-17.4.2.tgz#1277cb4d5395e9d6c39cbc351984bac9dcc6b7cd" - integrity sha512-HcymabrdBhsDMNzIv146+ZPNBPBK5gMNsVH+el2lCagnYgCi/4ixrHooeVyS64Fgce2K26+MC7OQ4vVH8wQWVw== +"@commitlint/lint@^17.7.0": + version "17.7.0" + resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-17.7.0.tgz#33f831298dc43679e4de6b088aea63d1f884c7e7" + integrity sha512-TCQihm7/uszA5z1Ux1vw+Nf3yHTgicus/+9HiUQk+kRSQawByxZNESeQoX9ujfVd3r4Sa+3fn0JQAguG4xvvbA== dependencies: - "@commitlint/is-ignored" "^17.4.2" - "@commitlint/parse" "^17.4.2" - "@commitlint/rules" "^17.4.2" - "@commitlint/types" "^17.4.0" + "@commitlint/is-ignored" "^17.7.0" + "@commitlint/parse" "^17.7.0" + "@commitlint/rules" "^17.7.0" + "@commitlint/types" "^17.4.4" -"@commitlint/load@^17.4.2": - version "17.4.2" - resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-17.4.2.tgz#551875c3e1dce6dc0375dc9c8ad551de8ba35de4" - integrity sha512-Si++F85rJ9t4hw6JcOw1i2h0fdpdFQt0YKwjuK4bk9KhFjyFkRxvR3SB2dPaMs+EwWlDrDBGL+ygip1QD6gmPw== +"@commitlint/load@^17.7.0": + version "17.7.0" + resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-17.7.0.tgz#2d69479bff3feec7966906988a39584aedd08574" + integrity sha512-jHKErVteyENYKkUiESNE6JYbaU4Sx58HXqv09/p7cKHzTtfyperlRvyqqTnxS8bas5Jjg4MP3MDZwdBrgRm8lw== dependencies: - "@commitlint/config-validator" "^17.4.0" + "@commitlint/config-validator" "^17.6.7" "@commitlint/execute-rule" "^17.4.0" - "@commitlint/resolve-extends" "^17.4.0" - "@commitlint/types" "^17.4.0" - "@types/node" "*" + "@commitlint/resolve-extends" "^17.6.7" + "@commitlint/types" "^17.4.4" chalk "^4.1.0" cosmiconfig "^8.0.0" - cosmiconfig-typescript-loader "^4.0.0" + cosmiconfig-typescript-loader "^5.0.0" lodash.isplainobject "^4.0.6" lodash.merge "^4.6.2" lodash.uniq "^4.5.0" resolve-from "^5.0.0" - ts-node "^10.8.1" - typescript "^4.6.4" "@commitlint/message@^17.4.2": version "17.4.2" resolved "https://registry.yarnpkg.com/@commitlint/message/-/message-17.4.2.tgz#f4753a79701ad6db6db21f69076e34de6580e22c" integrity sha512-3XMNbzB+3bhKA1hSAWPCQA3lNxR4zaeQAQcHj0Hx5sVdO6ryXtgUBGGv+1ZCLMgAPRixuc6en+iNAzZ4NzAa8Q== -"@commitlint/parse@^17.4.2": - version "17.4.2" - resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-17.4.2.tgz#b0f8a257a1f93387a497408b0b4cadba60ee3359" - integrity sha512-DK4EwqhxfXpyCA+UH8TBRIAXAfmmX4q9QRBz/2h9F9sI91yt6mltTrL6TKURMcjUVmgaB80wgS9QybNIyVBIJA== +"@commitlint/parse@^17.7.0": + version "17.7.0" + resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-17.7.0.tgz#aacb2d189e50ab8454154b1df150aaf20478ae47" + integrity sha512-dIvFNUMCUHqq5Abv80mIEjLVfw8QNuA4DS7OWip4pcK/3h5wggmjVnlwGCDvDChkw2TjK1K6O+tAEV78oxjxag== dependencies: - "@commitlint/types" "^17.4.0" - conventional-changelog-angular "^5.0.11" - conventional-commits-parser "^3.2.2" + "@commitlint/types" "^17.4.4" + conventional-changelog-angular "^6.0.0" + conventional-commits-parser "^4.0.0" -"@commitlint/read@^17.4.2": - version "17.4.2" - resolved "https://registry.yarnpkg.com/@commitlint/read/-/read-17.4.2.tgz#4880a05271fb44cefa54d365a17d5753496a6de0" - integrity sha512-hasYOdbhEg+W4hi0InmXHxtD/1favB4WdwyFxs1eOy/DvMw6+2IZBmATgGOlqhahsypk4kChhxjAFJAZ2F+JBg== +"@commitlint/read@^17.5.1": + version "17.5.1" + resolved "https://registry.yarnpkg.com/@commitlint/read/-/read-17.5.1.tgz#fec903b766e2c41e3cefa80630040fcaba4f786c" + integrity sha512-7IhfvEvB//p9aYW09YVclHbdf1u7g7QhxeYW9ZHSO8Huzp8Rz7m05aCO1mFG7G8M+7yfFnXB5xOmG18brqQIBg== dependencies: "@commitlint/top-level" "^17.4.0" - "@commitlint/types" "^17.4.0" + "@commitlint/types" "^17.4.4" fs-extra "^11.0.0" - git-raw-commits "^2.0.0" + git-raw-commits "^2.0.11" minimist "^1.2.6" -"@commitlint/resolve-extends@^17.4.0": - version "17.4.0" - resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-17.4.0.tgz#9023da6c70c4ebd173b4b0995fe29f27051da2d3" - integrity sha512-3JsmwkrCzoK8sO22AzLBvNEvC1Pmdn/65RKXzEtQMy6oYMl0Snrq97a5bQQEFETF0VsvbtUuKttLqqgn99OXRQ== +"@commitlint/resolve-extends@^17.6.7": + version "17.6.7" + resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-17.6.7.tgz#9c53a4601c96ab2dd20b90fb35c988639307735d" + integrity sha512-PfeoAwLHtbOaC9bGn/FADN156CqkFz6ZKiVDMjuC2N5N0740Ke56rKU7Wxdwya8R8xzLK9vZzHgNbuGhaOVKIg== dependencies: - "@commitlint/config-validator" "^17.4.0" - "@commitlint/types" "^17.4.0" + "@commitlint/config-validator" "^17.6.7" + "@commitlint/types" "^17.4.4" import-fresh "^3.0.0" lodash.mergewith "^4.6.2" resolve-from "^5.0.0" resolve-global "^1.0.0" -"@commitlint/rules@^17.4.2": - version "17.4.2" - resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-17.4.2.tgz#cdf203bc82af979cb319210ef9215cb1de216a9b" - integrity sha512-OGrPsMb9Fx3/bZ64/EzJehY9YDSGWzp81Pj+zJiY+r/NSgJI3nUYdlS37jykNIugzazdEXfMtQ10kmA+Kx2pZQ== +"@commitlint/rules@^17.7.0": + version "17.7.0" + resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-17.7.0.tgz#b97a4933c5cba11a659a19ee467f6f000f31533e" + integrity sha512-J3qTh0+ilUE5folSaoK91ByOb8XeQjiGcdIdiB/8UT1/Rd1itKo0ju/eQVGyFzgTMYt8HrDJnGTmNWwcMR1rmA== dependencies: - "@commitlint/ensure" "^17.4.0" + "@commitlint/ensure" "^17.6.7" "@commitlint/message" "^17.4.2" "@commitlint/to-lines" "^17.4.0" - "@commitlint/types" "^17.4.0" + "@commitlint/types" "^17.4.4" execa "^5.0.0" "@commitlint/to-lines@^17.4.0": @@ -177,81 +175,24 @@ dependencies: find-up "^5.0.0" -"@commitlint/types@^17.4.0": - version "17.4.0" - resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-17.4.0.tgz#c7c2b97b959f6175c164632bf26208ce417b3f31" - integrity sha512-2NjAnq5IcxY9kXtUeO2Ac0aPpvkuOmwbH/BxIm36XXK5LtWFObWJWjXOA+kcaABMrthjWu6la+FUpyYFMHRvbA== +"@commitlint/types@^17.4.4": + version "17.4.4" + resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-17.4.4.tgz#1416df936e9aad0d6a7bbc979ecc31e55dade662" + integrity sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ== dependencies: chalk "^4.1.0" -"@cspotcode/source-map-support@^0.8.0": - version "0.8.1" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" - integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== - dependencies: - "@jridgewell/trace-mapping" "0.3.9" - -"@jridgewell/resolve-uri@^3.0.3": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" - integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== - -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.14" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" - integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== - -"@jridgewell/trace-mapping@0.3.9": - version "0.3.9" - resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" - integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== - dependencies: - "@jridgewell/resolve-uri" "^3.0.3" - "@jridgewell/sourcemap-codec" "^1.4.10" - -"@solidity-parser/parser@^0.14.5": - version "0.14.5" - resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.5.tgz#87bc3cc7b068e08195c219c91cd8ddff5ef1a804" - integrity sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg== - dependencies: - antlr4ts "^0.5.0-alpha.4" - -"@tsconfig/node10@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" - integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== - -"@tsconfig/node12@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" - integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== - -"@tsconfig/node14@^1.0.0": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" - integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== - -"@tsconfig/node16@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" - integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== - "@types/minimist@^1.2.0": version "1.2.2" resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== -"@types/node@*": - version "18.11.18" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.18.tgz#8dfb97f0da23c2293e554c5a50d61ef134d7697f" - integrity sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA== - "@types/normalize-package-data@^2.4.0": version "2.4.1" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== -JSONStream@^1.0.4: +JSONStream@^1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== @@ -259,16 +200,6 @@ JSONStream@^1.0.4: jsonparse "^1.2.0" through ">=2.2.7 <3" -acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^8.4.1: - version "8.8.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a" - integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw== - ajv@^8.11.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" @@ -298,16 +229,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -antlr4ts@^0.5.0-alpha.4: - version "0.5.0-alpha.4" - resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" - integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -342,7 +263,7 @@ camelcase@^5.3.1: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -chalk@^2.0.0: +chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -400,13 +321,12 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" -conventional-changelog-angular@^5.0.11: - version "5.0.13" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" - integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== +conventional-changelog-angular@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz#a9a9494c28b7165889144fd5b91573c4aa9ca541" + integrity sha512-6qLgrBF4gueoC7AFVHu51nHL9pF9FRjXrH+ceVf7WmAfH3gs+gEYOkvxhjMPjZu57I4AGUGoNTY8V7Hrgf1uqg== dependencies: compare-func "^2.0.0" - q "^1.5.1" conventional-changelog-conventionalcommits@^5.0.0: version "5.0.0" @@ -417,38 +337,33 @@ conventional-changelog-conventionalcommits@^5.0.0: lodash "^4.17.15" q "^1.5.1" -conventional-commits-parser@^3.2.2: - version "3.2.4" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz#a7d3b77758a202a9b2293d2112a8d8052c740972" - integrity sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q== +conventional-commits-parser@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-4.0.0.tgz#02ae1178a381304839bce7cea9da5f1b549ae505" + integrity sha512-WRv5j1FsVM5FISJkoYMR6tPk07fkKT0UodruX4je86V4owk451yjXAKzKAPOs9l7y59E2viHUS9eQ+dfUA9NSg== dependencies: - JSONStream "^1.0.4" + JSONStream "^1.3.5" is-text-path "^1.0.1" - lodash "^4.17.15" - meow "^8.0.0" - split2 "^3.0.0" - through2 "^4.0.0" + meow "^8.1.2" + split2 "^3.2.2" -cosmiconfig-typescript-loader@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.3.0.tgz#c4259ce474c9df0f32274ed162c0447c951ef073" - integrity sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q== +cosmiconfig-typescript-loader@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-5.0.0.tgz#0d3becfe022a871f7275ceb2397d692e06045dc8" + integrity sha512-+8cK7jRAReYkMwMiG+bxhcNKiHJDM6bR9FD/nGBXOWdMLuYawjF5cGrtLilJ+LGd3ZjCXnJjR5DkfWPoIVlqJA== + dependencies: + jiti "^1.19.1" cosmiconfig@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.0.0.tgz#e9feae014eab580f858f8a0288f38997a7bebe97" - integrity sha512-da1EafcpH6b/TD8vDRaWV7xFINlHlF6zKsGwS1TsuVJTZRkquaS5HTMq7uq6h31619QjbsYl21gVDOm32KM1vQ== + version "8.2.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.2.0.tgz#f7d17c56a590856cd1e7cee98734dca272b0d8fd" + integrity sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ== dependencies: import-fresh "^3.2.1" js-yaml "^4.1.0" parse-json "^5.0.0" path-type "^4.0.0" -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -476,11 +391,6 @@ decamelize@^1.1.0: resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - dot-prop@^5.1.0: version "5.3.0" resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" @@ -547,9 +457,9 @@ find-up@^5.0.0: path-exists "^4.0.0" fs-extra@^11.0.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.0.tgz#5784b102104433bb0e090f48bfc4a30742c357ed" - integrity sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw== + version "11.1.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" + integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== dependencies: graceful-fs "^4.2.0" jsonfile "^6.0.1" @@ -570,7 +480,7 @@ get-stream@^6.0.0: resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== -git-raw-commits@^2.0.0: +git-raw-commits@^2.0.11: version "2.0.11" resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723" integrity sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A== @@ -589,9 +499,9 @@ global-dirs@^0.1.1: ini "^1.3.4" graceful-fs@^4.1.6, graceful-fs@^4.2.0: - version "4.2.10" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" - integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== hard-rejection@^2.1.0: version "2.1.0" @@ -665,10 +575,10 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg== -is-core-module@^2.5.0, is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== +is-core-module@^2.13.0, is-core-module@^2.5.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db" + integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ== dependencies: has "^1.0.3" @@ -704,6 +614,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +jiti@^1.19.1: + version "1.19.1" + resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.19.1.tgz#fa99e4b76a23053e0e7cde098efe1704a14c16f1" + integrity sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -826,11 +741,6 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - map-obj@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" @@ -841,7 +751,7 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== -meow@^8.0.0: +meow@^8.0.0, meow@^8.1.2: version "8.1.2" resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== @@ -883,9 +793,9 @@ minimist-options@4.1.0: kind-of "^6.0.3" minimist@^1.2.6: - version "1.2.7" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" - integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== + version "1.2.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" + integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== normalize-package-data@^2.5.0: version "2.5.0" @@ -991,20 +901,6 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -prettier-plugin-solidity@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.1.1.tgz#4d3375b85f97812ffcbe48d5a8b3fe914d69c91f" - integrity sha512-uD24KO26tAHF+zMN2nt1OUzfknzza5AgxjogQQrMLZc7j8xiQrDoNWNeOlfFC0YLTwo12CLD10b9niLyP6AqXg== - dependencies: - "@solidity-parser/parser" "^0.14.5" - semver "^7.3.8" - solidity-comments-extractor "^0.0.7" - -prettier@2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.6.2.tgz#e26d71a18a74c3d0f0597f55f01fb6c06c206032" - integrity sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew== - punycode@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f" @@ -1040,9 +936,9 @@ read-pkg@^5.2.0: type-fest "^0.6.0" readable-stream@3, readable-stream@^3.0.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -1084,11 +980,11 @@ resolve-global@1.0.0, resolve-global@^1.0.0: global-dirs "^0.1.1" resolve@^1.10.0: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + version "1.22.4" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34" + integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg== dependencies: - is-core-module "^2.9.0" + is-core-module "^2.13.0" path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" @@ -1098,14 +994,14 @@ safe-buffer@~5.2.0: integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== "semver@2 || 3 || 4 || 5": - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + version "5.7.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" + integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@7.3.8, semver@^7.3.4, semver@^7.3.8: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== +semver@7.5.4, semver@^7.3.4: + version "7.5.4" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" + integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" @@ -1126,15 +1022,10 @@ signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -solidity-comments-extractor@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" - integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== - spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== dependencies: spdx-expression-parse "^3.0.0" spdx-license-ids "^3.0.0" @@ -1153,11 +1044,11 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.12" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.12.tgz#69077835abe2710b65f03969898b6637b505a779" - integrity sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA== + version "3.0.13" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5" + integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w== -split2@^3.0.0: +split2@^3.0.0, split2@^3.2.2: version "3.2.2" resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== @@ -1240,25 +1131,6 @@ trim-newlines@^3.0.0: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== -ts-node@^10.8.1: - version "10.9.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" - integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== - dependencies: - "@cspotcode/source-map-support" "^0.8.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - v8-compile-cache-lib "^3.0.1" - yn "3.1.1" - type-fest@^0.18.0: version "0.18.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" @@ -1274,11 +1146,6 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -typescript@^4.6.4: - version "4.9.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78" - integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg== - universalify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" @@ -1296,11 +1163,6 @@ util-deprecate@^1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== -v8-compile-cache-lib@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" - integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== - validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" @@ -1346,9 +1208,9 @@ yargs-parser@^21.1.1: integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== yargs@^17.0.0: - version "17.6.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.2.tgz#2e23f2944e976339a1ee00f18c77fedee8332541" - integrity sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw== + version "17.7.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" + integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== dependencies: cliui "^8.0.1" escalade "^3.1.1" @@ -1358,11 +1220,6 @@ yargs@^17.0.0: y18n "^5.0.5" yargs-parser "^21.1.1" -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From 97fbd663fad61d7922020dfce7f07272056d2804 Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 9 Aug 2023 15:29:43 -0700 Subject: [PATCH 44/48] chore(workflows): updates workflow names --- .github/workflows/gas.yaml | 2 +- .github/workflows/optimized.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/gas.yaml b/.github/workflows/gas.yaml index 7f17b27b..377453d2 100644 --- a/.github/workflows/gas.yaml +++ b/.github/workflows/gas.yaml @@ -16,6 +16,6 @@ jobs: with: version: nightly - - name: Run gas profiling for RMM01Portfolio + - name: Run gas profiling run: FOUNDRY_PROFILE=optimized forge test --gas-report --match-contract TestGas diff --git a/.github/workflows/optimized.yaml b/.github/workflows/optimized.yaml index ade7c2bf..e0a34d1f 100644 --- a/.github/workflows/optimized.yaml +++ b/.github/workflows/optimized.yaml @@ -19,5 +19,5 @@ jobs: - name: Run build run: FOUNDRY_PROFILE=optimized forge build --sizes --skip test - - name: Run tests RMM01Portfolio + - name: Run tests run: FOUNDRY_PROFILE=optimized forge test From 4ddd6f91926a5678780e827cde8a7b88c5eef96a Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 9 Aug 2023 15:37:11 -0700 Subject: [PATCH 45/48] chore(package.json): updates npm pkg files and readme --- README.md | 7 ++++++- package.json | 7 +++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3d94b67b..520d2d87 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,12 @@ forge install ##### Testing ```bash -forge test +FOUNDRY_PROFILE=test forge test +``` + +##### Building +```bash +FOUNDRY_PROFILE=optimized forge build --skip test ``` ##### Coverage diff --git a/package.json b/package.json index 017a9546..b02b842c 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,17 @@ "name": "@primitivexyz/portfolio", "license": "AGPL-3.0-only", "version": "v1.4.0-beta", - "description": "Onchain protocol for low cost portfolio management using automated market making strategies.", + "description": "Portfolio is an automated market making protocol for implementing custom liquidity distribution strategies at the lowest cost possible. ", "publishConfig": { "access": "public" }, "files": [ "contracts/**/*.sol", + "optimized-out/NormalStrategy.sol/NormalStrategy.json", "optimized-out/Portfolio.sol/Portfolio.json", - "optimized-out/RMM01Portfolio.sol/RMM01Portfolio.json", + "optimized-out/PositionRenderer.sol/PositionRenderer.json", + "optimized-out/IStrategy.sol/IStrategy.json", + "optimized-out/INormalStrategy.sol/INormalStrategy.json", "optimized-out/IPortfolio.sol/IPortfolio.json", "optimized-out/IPortfolioRegistry.sol/IPortfolioRegistry.json" ], From 63f10a47c741c8e05accbda96668d936fb7ba36b Mon Sep 17 00:00:00 2001 From: alex Date: Wed, 9 Aug 2023 15:52:25 -0700 Subject: [PATCH 46/48] docs(readme): updates readme with links to audits --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 520d2d87..768ea51e 100644 --- a/README.md +++ b/README.md @@ -46,13 +46,13 @@ Read the local [docs](./docs/src/), hosted docs [docs.primitive.xyz](https://doc ## Audits -| Security Firm | Date | Review Time | Status | Final Commit w/ Fixes | -| --------------------- | ---------- | ----------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------- | -| ChainSecurity | 2022-05-31 | 8-weeks | Completed | [c6f692f41c1d20ac09acb832923bd46500fd8e06](https://github.com/primitivefinance/portfolio/commit/c6f692f41c1d20ac09acb832923bd46500fd8e06) | -| Trail of Bits | 2023-01-31 | 8-weeks | Completed | n/a | -| Spearbit #1 | 2023-03-31 | 5-weeks | Completed | [tag/v1.1.0-beta](https://github.com/primitivefinance/portfolio/releases/tag/v1.1.0-beta) | -| Spearbit #1 Extension | 2023-05-12 | 2-weeks | Competed | [36e9efa28332fa03f6d5910edda2fec2f5937190](https://github.com/primitivefinance/portfolio/commit/36e9efa28332fa03f6d5910edda2fec2f5937190 ) | -| Spearbit #2 | 2023-07-78 | 2-weeks | Completed | [tag/v1.4.0-beta-spearbit-2023-08-complete](https://github.com/primitivefinance/portfolio/releases/tag/v1.4.0-beta-spearbit-2023-08-complete) | +| Security Firm | Date | Review Time | Status | Final Commit w/ Fixes | +| ------------------------------------------------------------------------------------------------------------- | ---------- | ----------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------- | +| [ChainSecurity](https://github.com/primitivefinance/security/blob/main/audits/portfolio/chainsecurity.pdf) | 2022-05-31 | 8-weeks | Completed | [c6f692f41c1d20ac09acb832923bd46500fd8e06](https://github.com/primitivefinance/portfolio/commit/c6f692f41c1d20ac09acb832923bd46500fd8e06) | +| [Trail of Bits](https://github.com/primitivefinance/security/blob/main/audits/portfolio/trailofbits.pdf) | 2023-01-31 | 8-weeks | Completed | n/a | +| [Spearbit #1](https://github.com/primitivefinance/security/blob/main/audits/portfolio/spearbit.pdf) | 2023-03-31 | 5-weeks | Completed | [tag/v1.1.0-beta](https://github.com/primitivefinance/portfolio/releases/tag/v1.1.0-beta) | +| [Spearbit #1 Extension](https://github.com/primitivefinance/security/blob/main/audits/portfolio/spearbit.pdf) | 2023-05-12 | 2-weeks | Competed | [36e9efa28332fa03f6d5910edda2fec2f5937190](https://github.com/primitivefinance/portfolio/commit/36e9efa28332fa03f6d5910edda2fec2f5937190 ) | +| Spearbit #2 | 2023-07-78 | 2-weeks | Completed | [tag/v1.4.0-beta-spearbit-2023-08-complete](https://github.com/primitivefinance/portfolio/releases/tag/v1.4.0-beta-spearbit-2023-08-complete) | ## Install From cace54fd1dc615770a1384ee88bebd7a33283a71 Mon Sep 17 00:00:00 2001 From: alex Date: Fri, 11 Aug 2023 08:26:53 -0700 Subject: [PATCH 47/48] fix(#434): adds gas snapshot --- .gas-snapshot | 84 ++++++++++++++++++++------------------------------- 1 file changed, 32 insertions(+), 52 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 589d377c..18b671ac 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,52 +1,32 @@ -TestRMM01:testFuzz_changeParameters(uint16,uint16,uint16) (runs: 10000, μ: 363354, ~: 363518) -TestRMM01:testFuzz_createPool(uint16,uint16,uint16,uint16,uint16,uint128,uint128) (runs: 10000, μ: 201062, ~: 190373) -TestRMM01:testFuzz_fund_then_draw(uint64) (runs: 10000, μ: 640427, ~: 640432) -TestRMM01:test_allocate_balances_increase() (gas: 747698) -TestRMM01:test_allocate_does_not_modify_timestamp() (gas: 721218) -TestRMM01:test_allocate_liquidity_overflow_reverts() (gas: 333599) -TestRMM01:test_allocate_modifies_liquidity() (gas: 731248) -TestRMM01:test_allocate_non_existent_pool_reverts() (gas: 46168) -TestRMM01:test_allocate_reserves_increase() (gas: 748973) -TestRMM01:test_allocate_reserves_increase_six_decimals() (gas: 749333) -TestRMM01:test_allocate_zero_liquidity_reverts() (gas: 333596) -TestRMM01:test_changeParameters_priority_fee_success() (gas: 363537) -TestRMM01:test_claim_credits_balance_asset() (gas: 1739536) -TestRMM01:test_claim_get_balance_returns_fee_amount_asset() (gas: 1126889) -TestRMM01:test_claim_get_balance_returns_fee_amount_quote() (gas: 1267102) -TestRMM01:test_claim_position_owed_amounts_returns_zero() (gas: 1138770) -TestRMM01:test_claim_small_liquidity_does_not_steal_fees() (gas: 1835588) -TestRMM01:test_claim_succeeds() (gas: 1552908) -TestRMM01:test_createPair_fetch_pair_data_returns_token_data() (gas: 1482969) -TestRMM01:test_createPair_fetch_pair_id_returns_non_zero() (gas: 1478372) -TestRMM01:test_createPair_nonce_increments_returns_one() (gas: 1478880) -TestRMM01:test_createPair_success() (gas: 1476912) -TestRMM01:test_createPool_non_controlled_default_jit() (gas: 183374) -TestRMM01:test_deallocate_max() (gas: 900263) -TestRMM01:test_deposit_ether_balance_equals_zero() (gas: 159134) -TestRMM01:test_deposit_ether_balance_of_weth_equals_msg_value() (gas: 159696) -TestRMM01:test_deposit_increases_user_weth_balance() (gas: 167619) -TestRMM01:test_deposit_reserve_equals_msg_value() (gas: 164251) -TestRMM01:test_deposit_weth_total_supply_equals_msg_value() (gas: 161475) -TestRMM01:test_draw_max_balance() (gas: 557773) -TestRMM01:test_draw_reduces_user_balance() (gas: 557859) -TestRMM01:test_draw_weth_transfers_ether() (gas: 242314) -TestRMM01:test_fund_increases_user_balance() (gas: 482493) -TestRMM01:test_fund_max_balance() (gas: 482492) -TestRMM01:test_revert_changeParameters_invalid_fee() (gas: 348973) -TestRMM01:test_revert_changeParameters_invalid_jit() (gas: 348576) -TestRMM01:test_revert_changeParameters_not_controller() (gas: 347545) -TestRMM01:test_revert_changeParameters_priority_fee_above_max() (gas: 360133) -TestRMM01:test_revert_claim_no_position() (gas: 336074) -TestRMM01:test_revert_createPair_asset_lower_decimal_bound() (gas: 1387955) -TestRMM01:test_revert_createPair_asset_upper_decimal_bound() (gas: 1388031) -TestRMM01:test_revert_createPair_exists() (gas: 346704) -TestRMM01:test_revert_createPair_quote_lower_decimal_bound() (gas: 1388035) -TestRMM01:test_revert_createPair_quote_upper_decimal_bound() (gas: 1388111) -TestRMM01:test_revert_createPair_same_token() (gas: 699838) -TestRMM01:test_revert_createPool_above_max_pairs() (gas: 1033031) -TestRMM01:test_revert_createPool_above_max_pools() (gas: 27892) -TestRMM01:test_revert_createPool_priority_fee_invalid_fee() (gas: 65236) -TestRMM01:test_revert_createPool_zero_price() (gas: 19257) -TestRMM01:test_revert_draw_greater_than_balance() (gas: 339917) -TestRMM01:test_swap_increases_user_balance_token_out() (gas: 1107628) -TestRMM01:test_version() (gas: 6969) \ No newline at end of file +TestGas:test_gas_chain_allocate_deallocate_from_portfolio_balance() (gas: 155363) +TestGas:test_gas_chain_create_allocate_from_portfolio() (gas: 460784) +TestGas:test_gas_chain_swap_allocate_from_portfolio() (gas: 327801) +TestGas:test_gas_chain_swap_deallocate_create_allocate_from_portfolio() (gas: 470302) +TestGas:test_gas_create_pool_allocate_transfer_from_wallet() (gas: 460492) +TestGas:test_gas_multi_allocate_10() (gas: 853389) +TestGas:test_gas_multi_allocate_100() (gas: 7461628) +TestGas:test_gas_multi_allocate_2() (gas: 395302) +TestGas:test_gas_multi_allocate_25() (gas: 1769294) +TestGas:test_gas_multi_allocate_2_pairs() (gas: 515736) +TestGas:test_gas_multi_allocate_5() (gas: 564596) +TestGas:test_gas_multi_allocate_50() (gas: 3460599) +TestGas:test_gas_multi_create_pool_100() (gas: 264444) +TestGas:test_gas_multi_deallocate_10() (gas: 448246) +TestGas:test_gas_multi_deallocate_100() (gas: 4156306) +TestGas:test_gas_multi_deallocate_2() (gas: 264442) +TestGas:test_gas_multi_deallocate_25() (gas: 857111) +TestGas:test_gas_multi_deallocate_2_pool_2_pair() (gas: 365064) +TestGas:test_gas_multi_deallocate_5() (gas: 330573) +TestGas:test_gas_multi_deallocate_50() (gas: 1724360) +TestGas:test_gas_multi_swap_10() (gas: 1100287) +TestGas:test_gas_multi_swap_100() (gas: 12966986) +TestGas:test_gas_multi_swap_2() (gas: 389117) +TestGas:test_gas_multi_swap_25() (gas: 2788271) +TestGas:test_gas_multi_swap_5() (gas: 641773) +TestGas:test_gas_multi_swap_50() (gas: 5859558) +TestGas:test_gas_single_allocate() (gas: 339512) +TestGas:test_gas_single_allocate_from_portfolio_balance() (gas: 339459) +TestGas:test_gas_single_deallocate() (gas: 244731) +TestGas:test_gas_single_swap() (gas: 307095) +TestGas:test_gas_single_swap_from_portfolio_balance() (gas: 305376) +TestGas:test_gas_single_swap_from_wallet() (gas: 307011) \ No newline at end of file From 6791f4c94c61793c52e82b25971c277fa15bcffd Mon Sep 17 00:00:00 2001 From: alex Date: Thu, 31 Aug 2023 10:39:42 -0700 Subject: [PATCH 48/48] test(ethsp): adds ethsp test --- contracts/test/ERC20Wrapper.sol | 48 ++++++ test/ETHSP.t.sol | 295 ++++++++++++++++++++++++++++++++ 2 files changed, 343 insertions(+) create mode 100644 contracts/test/ERC20Wrapper.sol create mode 100644 test/ETHSP.t.sol diff --git a/contracts/test/ERC20Wrapper.sol b/contracts/test/ERC20Wrapper.sol new file mode 100644 index 00000000..1c5ee720 --- /dev/null +++ b/contracts/test/ERC20Wrapper.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity 0.8.19; + +import "solmate/tokens/ERC20.sol"; +import "solmate/tokens/ERC1155.sol"; + +contract ERC20Wrapper is ERC20, ERC1155TokenReceiver { + address immutable PORTFOLIO; + uint256[] public POOL_IDS; + + constructor( + address portfolio_, + uint64[] memory poolIds_, + string memory name_, + string memory symbol_ + ) ERC20(name_, symbol_, 18) { + PORTFOLIO = portfolio_; + POOL_IDS = poolIds_; + } + + function mint(address to, uint256 amount) external { + uint256[] memory amounts = new uint256[](POOL_IDS.length); + + for (uint256 i = 0; i < POOL_IDS.length; i++) { + amounts[i] = amount; + } + + ERC1155(PORTFOLIO).safeBatchTransferFrom( + msg.sender, address(this), POOL_IDS, amounts, "" + ); + + _mint(to, amount); + } + + function burn(address to, uint256 amount) external { + _burn(msg.sender, amount); + + uint256[] memory amounts = new uint256[](POOL_IDS.length); + + for (uint256 i = 0; i < POOL_IDS.length; i++) { + amounts[i] = amount; + } + + ERC1155(PORTFOLIO).safeBatchTransferFrom( + address(this), to, POOL_IDS, amounts, "" + ); + } +} diff --git a/test/ETHSP.t.sol b/test/ETHSP.t.sol new file mode 100644 index 00000000..029420a1 --- /dev/null +++ b/test/ETHSP.t.sol @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.4; + +import "solmate/test/utils/mocks/MockERC20.sol"; +import "solmate/utils/SafeCastLib.sol"; +import "../contracts/test/ERC20Wrapper.sol"; + +import "./Setup.sol"; + +using SafeCastLib for uint256; + +function to128(uint256 x) pure returns (uint128 y) { + y = x.safeCastTo128(); +} + +contract ETHSP is Setup { + using { to128 } for uint256; + using NormalConfiguration for Configuration; + + Configuration CONFIG; + + address TOKEN_0; + address TOKEN_1; + address TOKEN_2; + address TOKEN_3; + + uint64 TRANCHE_A_POOL; + uint64 TRANCHE_B_POOL; + + address TRANCHE_A_TOKEN; + address TRANCHE_B_TOKEN; + + address ETHSP_TOKEN; + + address PRIMARY_MARKET_USER; + + function create_actor() public { + PRIMARY_MARKET_USER = address(0x0444); + } + + modifier usePrimaryMarketUser() { + vm.startPrank(PRIMARY_MARKET_USER); + _; + vm.stopPrank(); + } + + /// @dev ETHSP is a pool of two liquidity pool tokens. + function test_ethsp() public { + create_actor(); + + _create_tokens(); + _create_pairs(); + _create_tranche_pools(); + _tokenize_tranches(); + _tokenize_ethsp(); + + approve_tokens(); + allocate(); + issue(); + rebalance(); + } + + function approve_tokens() public usePrimaryMarketUser { + // Approves tokens to subject (portfolio) + MockERC20(TOKEN_0).approve(address(subject()), type(uint256).max); + MockERC20(TOKEN_1).approve(address(subject()), type(uint256).max); + MockERC20(TOKEN_2).approve(address(subject()), type(uint256).max); + MockERC20(TOKEN_3).approve(address(subject()), type(uint256).max); + + // Approve tokenized pools to subject (portfolio) + MockERC20(TRANCHE_A_TOKEN).approve( + address(subject()), type(uint256).max + ); + MockERC20(TRANCHE_B_TOKEN).approve( + address(subject()), type(uint256).max + ); + MockERC20(ETHSP_TOKEN).approve(address(subject()), type(uint256).max); + } + + function allocate() public usePrimaryMarketUser { + // Allocate 10 liquidity to each tranche + uint256 amount = 10 ether; + + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSelector( + IPortfolioActions.allocate.selector, + false, + PRIMARY_MARKET_USER, + TRANCHE_A_POOL, + amount, + type(uint128).max, + type(uint128).max + ); + + calls[1] = abi.encodeWithSelector( + IPortfolioActions.allocate.selector, + false, + PRIMARY_MARKET_USER, + TRANCHE_B_POOL, + amount, + type(uint128).max, + type(uint128).max + ); + + subject().multicall(calls); + } + + function issue() public usePrimaryMarketUser { + // Gets amounts of liquidity to wrap into ETHSP token. + uint256 amount = 1 ether; + ERC1155(address(subject())).setApprovalForAll(ETHSP_TOKEN, true); + ERC1155(address(subject())).setApprovalForAll(ETHSP_TOKEN, true); + ERC20Wrapper(ETHSP_TOKEN).mint(PRIMARY_MARKET_USER, amount); + } + + function rebalance() public usePrimaryMarketUser { + // Assume I receive 1 ETHSP token. + // For example, I purchase it on a DEX for $0.9, + // but the underlying assets are worth $1.0. + // I want to redeem it for the underlying assest and sell it into the market. + + uint256[4] memory startBalances = [ + MockERC20(TOKEN_0).balanceOf(PRIMARY_MARKET_USER), + MockERC20(TOKEN_1).balanceOf(PRIMARY_MARKET_USER), + MockERC20(TOKEN_2).balanceOf(PRIMARY_MARKET_USER), + MockERC20(TOKEN_3).balanceOf(PRIMARY_MARKET_USER) + ]; + + // Redeem ETHSP + uint256 amount = 1 ether; + ERC20Wrapper(ETHSP_TOKEN).burn(PRIMARY_MARKET_USER, amount); + + // Remove liquidity from both ERC115 pool tokens. + bytes[] memory calls = new bytes[](2); + calls[0] = abi.encodeWithSelector( + IPortfolioActions.deallocate.selector, + false, + TRANCHE_A_POOL, + amount, + 0, + 0 + ); + calls[1] = abi.encodeWithSelector( + IPortfolioActions.deallocate.selector, + false, + TRANCHE_B_POOL, + amount, + 0, + 0 + ); + + subject().multicall(calls); + + uint256[4] memory endBalances = [ + MockERC20(TOKEN_0).balanceOf(PRIMARY_MARKET_USER), + MockERC20(TOKEN_1).balanceOf(PRIMARY_MARKET_USER), + MockERC20(TOKEN_2).balanceOf(PRIMARY_MARKET_USER), + MockERC20(TOKEN_3).balanceOf(PRIMARY_MARKET_USER) + ]; + + uint256[4] memory deltas = [ + endBalances[0] - startBalances[0], + endBalances[1] - startBalances[1], + endBalances[2] - startBalances[2], + endBalances[3] - startBalances[3] + ]; + + console2.log("ethsp delta: -", amount); + for (uint256 i; i < deltas.length; i++) { + emit delta(i, deltas[i]); + } + + // Send the tokens to pay the DEX, or anyone else, back! + } + + event delta(uint256 i, uint256 amt); + + function _create_tokens() internal { + // Basket A + TOKEN_0 = address(new MockERC20("Token0", "0", 18)); + TOKEN_1 = address(new MockERC20("Token1", "1", 18)); + + // Basket B + TOKEN_2 = address(new MockERC20("Token2", "2", 18)); + TOKEN_3 = address(new MockERC20("Token3", "3", 18)); + + // Labels + vm.label(TOKEN_0, "Token0"); + vm.label(TOKEN_1, "Token1"); + vm.label(TOKEN_2, "Token2"); + vm.label(TOKEN_3, "Token3"); + + // Mint some tokens to primary market user. + MockERC20(TOKEN_0).mint(PRIMARY_MARKET_USER, 1_000_000_000 ether); + MockERC20(TOKEN_1).mint(PRIMARY_MARKET_USER, 1_000_000_000 ether); + MockERC20(TOKEN_2).mint(PRIMARY_MARKET_USER, 1_000_000_000 ether); + MockERC20(TOKEN_3).mint(PRIMARY_MARKET_USER, 1_000_000_000 ether); + } + + function _create_pairs() internal { + // Create both pairs + uint24 TRANCHE_A_PAIR = subject().createPair(TOKEN_0, TOKEN_1); + uint24 TRANCHE_B_PAIR = subject().createPair(TOKEN_2, TOKEN_3); + } + + function _create_tranche_pools() internal { + // Create both pools + uint256 volatility_bps = 1000; + uint256 duration_sec = SECONDS_PER_YEAR; + uint256 strike_price = 2000 ether; + uint256 last_price = 1800 ether; + uint256 fee_bps = 10; + uint256 priority_fee_bps = 0; + + // Pool config is specific to the NormalStrategy. + PortfolioConfig memory POOL_CONFIG = PortfolioConfig({ + strikePriceWad: strike_price.to128(), + volatilityBasisPoints: volatility_bps.safeCastTo32(), + durationSeconds: duration_sec.safeCastTo32(), + isPerpetual: false, + creationTimestamp: uint32(block.timestamp) + }); + + // Makes a defaut config which defines the Portfolio pool state. + CONFIG = configure(); + // Edits the fee of the pool. + CONFIG = CONFIG.edit("feeBasisPoints", abi.encode(fee_bps)).edit( + "priorityFeeBasisPoints", abi.encode(priority_fee_bps) + ); + // Adds the strategy specific arguments to the config, by computing the reserves and also encoding the strategy data. + CONFIG = CONFIG.combine(POOL_CONFIG); + // Updates the strategy to have the reserves match a price. + CONFIG = CONFIG.editStrategy("priceWad", abi.encode(last_price)); + // Applies the respective tokens to each config. + Configuration memory TRANCHE_A_CONFIG = CONFIG.edit( + "asset", abi.encode(TOKEN_0) + ).edit("quote", abi.encode(TOKEN_1)); + + Configuration memory TRANCHE_B_CONFIG = CONFIG.edit( + "asset", abi.encode(TOKEN_2) + ).edit("quote", abi.encode(TOKEN_3)); + // Creates the pools with the configs. + TRANCHE_A_POOL = TRANCHE_A_CONFIG.activate( + address(subject()), NormalConfiguration.validateNormalStrategy + ); + TRANCHE_B_POOL = TRANCHE_B_CONFIG.activate( + address(subject()), NormalConfiguration.validateNormalStrategy + ); + } + + function _tokenize_tranches() public { + uint64[] memory a_pools = new uint64[](1); + a_pools[0] = TRANCHE_A_POOL; + TRANCHE_A_TOKEN = address( + new ERC20Wrapper( + address(subject()), + a_pools, + "ETHSP Tranche A", + "ETHSP-A" + ) + ); + + uint64[] memory b_pools = new uint64[](1); + b_pools[0] = TRANCHE_B_POOL; + TRANCHE_B_TOKEN = address( + new ERC20Wrapper( + address(subject()), + b_pools, + "ETHSP Tranche B", + "ETHSP-B" + ) + ); + + // Labels + vm.label(TRANCHE_A_TOKEN, "ETHSP Tranche A"); + vm.label(TRANCHE_B_TOKEN, "ETHSP Tranche B"); + } + + function _tokenize_ethsp() public { + uint64[] memory ethsp_pools = new uint64[](2); + ethsp_pools[0] = TRANCHE_A_POOL; + ethsp_pools[1] = TRANCHE_B_POOL; + ETHSP_TOKEN = address( + new ERC20Wrapper( + address(subject()), + ethsp_pools, + "ETHSP", + "ETHSP" + ) + ); + + // Labels + vm.label(ETHSP_TOKEN, "ETHSP"); + } +}