From 94144ef71d8775eaf70f5cbd81aa160b98c93394 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Sun, 12 Jun 2022 11:11:11 +0200 Subject: [PATCH 01/30] chore: forge init --- .github/workflows/test.yml | 34 ++++++++++++++++++++++++++++++++++ .gitignore | 2 ++ foundry.toml | 6 ++++++ script/Contract.s.sol | 15 +++++++++++++++ src/Contract.sol | 4 ++++ test/Contract.t.sol | 12 ++++++++++++ 6 files changed, 73 insertions(+) create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100644 foundry.toml create mode 100644 script/Contract.s.sol create mode 100644 src/Contract.sol create mode 100644 test/Contract.t.sol diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..09880b1d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,34 @@ +name: test + +on: workflow_dispatch + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run Forge build + run: | + forge --version + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..d8a1d071 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +cache/ +out/ diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 00000000..d7dd144b --- /dev/null +++ b/foundry.toml @@ -0,0 +1,6 @@ +[default] +src = 'src' +out = 'out' +libs = ['lib'] + +# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file diff --git a/script/Contract.s.sol b/script/Contract.s.sol new file mode 100644 index 00000000..57c97e50 --- /dev/null +++ b/script/Contract.s.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Script.sol"; + +import {Contract} from "src/Contract.sol"; + +contract ContractScript is Script { + function setUp() public {} + + function run() public { + vm.broadcast(); + new Contract(); + } +} diff --git a/src/Contract.sol b/src/Contract.sol new file mode 100644 index 00000000..655b543e --- /dev/null +++ b/src/Contract.sol @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Contract {} diff --git a/test/Contract.t.sol b/test/Contract.t.sol new file mode 100644 index 00000000..bfaaad95 --- /dev/null +++ b/test/Contract.t.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import "forge-std/Test.sol"; + +contract ContractTest is Test { + function setUp() public {} + + function testExample() public { + assertTrue(true); + } +} From 9dd17511785d306cfe4d98dbadf61330a3646394 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Sun, 12 Jun 2022 11:11:14 +0200 Subject: [PATCH 02/30] forge install: forge-std --- .gitmodules | 3 +++ lib/forge-std | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 lib/forge-std diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..888d42dc --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 00000000..e0393ac5 --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit e0393ac558fa62731e7890677c5f0559a06e39c2 From 2037b886d33c3b6c3f21f01af61c77a8dd27dbc8 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Sun, 12 Jun 2022 11:31:49 +0200 Subject: [PATCH 03/30] Setup connector contract --- script/Connector.s.sol | 17 +++++++++++++++++ script/Contract.s.sol | 15 --------------- src/Connector.sol | 21 +++++++++++++++++++++ src/Contract.sol | 4 ---- src/Router.sol | 15 +++++++++++++++ test/{Contract.t.sol => Connector.t.sol} | 4 ++-- 6 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 script/Connector.s.sol delete mode 100644 script/Contract.s.sol create mode 100644 src/Connector.sol delete mode 100644 src/Contract.sol create mode 100644 src/Router.sol rename test/{Contract.t.sol => Connector.t.sol} (75%) diff --git a/script/Connector.s.sol b/script/Connector.s.sol new file mode 100644 index 00000000..06e42f19 --- /dev/null +++ b/script/Connector.s.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.7.6; + +import "forge-std/Script.sol"; + +import {Router} from "src/Router.sol"; +import {Connector} from "src/Connector.sol"; + +contract ConnectorScript is Script { + function setUp() public {} + + function run() public { + vm.broadcast(); + Router router = new Router(); + new Connector(address(router)); + } +} diff --git a/script/Contract.s.sol b/script/Contract.s.sol deleted file mode 100644 index 57c97e50..00000000 --- a/script/Contract.s.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import "forge-std/Script.sol"; - -import {Contract} from "src/Contract.sol"; - -contract ContractScript is Script { - function setUp() public {} - - function run() public { - vm.broadcast(); - new Contract(); - } -} diff --git a/src/Connector.sol b/src/Connector.sol new file mode 100644 index 00000000..5f47440b --- /dev/null +++ b/src/Connector.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.7.6; + +interface ConnectorRouterLike { + function updateInvestOrder(uint poolId, uint[] calldata trancheId, uint amount) external; +} + +contract Connector { + + ConnectorRouterLike public immutable router; + + constructor(address router_) { + router = ConnectorRouterLike(router_); + } + + function updateInvestOrder(uint poolId, uint[] calldata trancheId, uint amount) external { + // TODO: check pool_id is valid, check tranche_id is valid, transfer amount of pool currency to self + router.updateInvestOrder(poolId, trancheId, amount); + } + +} diff --git a/src/Contract.sol b/src/Contract.sol deleted file mode 100644 index 655b543e..00000000 --- a/src/Contract.sol +++ /dev/null @@ -1,4 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Contract {} diff --git a/src/Router.sol b/src/Router.sol new file mode 100644 index 00000000..eb207f3e --- /dev/null +++ b/src/Router.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.7.6; + +import "forge-std/Test.sol"; + +contract Router is Test { + + function updateInvestOrder(uint poolId, uint[] calldata trancheId, uint amount) external { + console.log(poolId); + console.log(amount); + // TODO: send message to Nomad Home contract + return; + } + +} diff --git a/test/Contract.t.sol b/test/Connector.t.sol similarity index 75% rename from test/Contract.t.sol rename to test/Connector.t.sol index bfaaad95..b82cb4be 100644 --- a/test/Contract.t.sol +++ b/test/Connector.t.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; +pragma solidity ^0.7.6; import "forge-std/Test.sol"; -contract ContractTest is Test { +contract ConnectorTest is Test { function setUp() public {} function testExample() public { From 6f1b342df80bf1ace4ab1881286aea210624a38d Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Sun, 12 Jun 2022 11:33:03 +0200 Subject: [PATCH 04/30] forge install: monorepo --- .gitmodules | 3 +++ lib/monorepo | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/monorepo diff --git a/.gitmodules b/.gitmodules index 888d42dc..4d20d9d9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/monorepo"] + path = lib/monorepo + url = https://github.com/nomad-xyz/monorepo diff --git a/lib/monorepo b/lib/monorepo new file mode 160000 index 00000000..09a9c247 --- /dev/null +++ b/lib/monorepo @@ -0,0 +1 @@ +Subproject commit 09a9c24728920b66a9013bb2e319b2c736991dad From c455f5b38baf3044185a823f8eeb6c05c69e3962 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Sun, 12 Jun 2022 14:15:39 +0200 Subject: [PATCH 05/30] Add nomad dependency --- remappings.txt | 1 + script/Connector.s.sol | 8 ++++---- src/Connector.sol | 8 ++++---- src/Router.sol | 27 ++++++++++++++++++++------- 4 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 remappings.txt diff --git a/remappings.txt b/remappings.txt new file mode 100644 index 00000000..48dad861 --- /dev/null +++ b/remappings.txt @@ -0,0 +1 @@ +@nomad-xyz/contracts-router/=lib/monorepo/packages/contracts-router/ \ No newline at end of file diff --git a/script/Connector.s.sol b/script/Connector.s.sol index 06e42f19..6e93289c 100644 --- a/script/Connector.s.sol +++ b/script/Connector.s.sol @@ -3,15 +3,15 @@ pragma solidity ^0.7.6; import "forge-std/Script.sol"; -import {Router} from "src/Router.sol"; -import {Connector} from "src/Connector.sol"; +import {ConnectorRouter} from "src/Router.sol"; +import {CentrifugeConnector} from "src/Connector.sol"; contract ConnectorScript is Script { function setUp() public {} function run() public { vm.broadcast(); - Router router = new Router(); - new Connector(address(router)); + ConnectorRouter router = new ConnectorRouter(); + new CentrifugeConnector(address(router)); } } diff --git a/src/Connector.sol b/src/Connector.sol index 5f47440b..3b741785 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -1,16 +1,16 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.7.6; -interface ConnectorRouterLike { +interface RouterLike { function updateInvestOrder(uint poolId, uint[] calldata trancheId, uint amount) external; } -contract Connector { +contract CentrifugeConnector { - ConnectorRouterLike public immutable router; + RouterLike public immutable router; constructor(address router_) { - router = ConnectorRouterLike(router_); + router = RouterLike(router_); } function updateInvestOrder(uint poolId, uint[] calldata trancheId, uint amount) external { diff --git a/src/Router.sol b/src/Router.sol index eb207f3e..96a3f5b3 100644 --- a/src/Router.sol +++ b/src/Router.sol @@ -2,14 +2,27 @@ pragma solidity ^0.7.6; import "forge-std/Test.sol"; +import {Router} from "@nomad-xyz/contracts-router/contracts/Router.sol"; -contract Router is Test { +contract ConnectorRouter is Router, Test { + uint32 immutable CENTRIFUGE_CHAIN_DOMAIN = 3000; - function updateInvestOrder(uint poolId, uint[] calldata trancheId, uint amount) external { - console.log(poolId); - console.log(amount); - // TODO: send message to Nomad Home contract - return; - } + function updateInvestOrder( + uint256 poolId, + uint256[] calldata trancheId, + uint256 amount + ) external { + console.log(poolId); + console.log(amount); + // TODO: send message to Nomad Home contract + return; + } + function send(bytes memory message) internal { + (_home()).dispatch( + CENTRIFUGE_CHAIN_DOMAIN, + _mustHaveRemote(CENTRIFUGE_CHAIN_DOMAIN), + message + ); + } } From 4daba724a4ac27ba7a506166cf20ad4d905d3e4c Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Sun, 12 Jun 2022 14:15:52 +0200 Subject: [PATCH 06/30] forge install: openzeppelin-contracts-upgradeable --- .gitmodules | 3 +++ lib/openzeppelin-contracts-upgradeable | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index 4d20d9d9..28158a3a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/monorepo"] path = lib/monorepo url = https://github.com/nomad-xyz/monorepo +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 00000000..d86d4540 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit d86d45402e060579c35cf4557c2e54845a798519 From 7a3bbc2e65c6503cd7651bb83b65b2cde58251b1 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Sun, 12 Jun 2022 15:23:22 +0200 Subject: [PATCH 07/30] Add config, deploy script --- .env.example | 3 +++ .gitignore | 2 ++ deploy.sh | 8 ++++++++ foundry.toml | 2 +- remappings.txt | 10 +++++++++- src/Router.sol | 18 +++++++++--------- 6 files changed, 32 insertions(+), 11 deletions(-) create mode 100644 .env.example create mode 100644 deploy.sh diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..80a258a0 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +RPC_URL= +PRIVATE_KEY= +ETHERSCAN_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore index d8a1d071..73252fc4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ cache/ out/ +broadcast/ +.env \ No newline at end of file diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 00000000..cae1d2bb --- /dev/null +++ b/deploy.sh @@ -0,0 +1,8 @@ +source .env + +if [ -z $ETHERSCAN_KEY ]; then + forge script script/Connector.s.sol:ConnectorScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast -vvvv +else + forge script script/Connector.s.sol:ConnectorScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --verify --etherscan-api-key $ETHERSCAN_KEY -vvvv +fi + diff --git a/foundry.toml b/foundry.toml index d7dd144b..b03c6a74 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,4 +3,4 @@ src = 'src' out = 'out' libs = ['lib'] -# See more config options https://github.com/foundry-rs/foundry/tree/master/config \ No newline at end of file +solc_version = "0.7.6" \ No newline at end of file diff --git a/remappings.txt b/remappings.txt index 48dad861..294526cd 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1 +1,9 @@ -@nomad-xyz/contracts-router/=lib/monorepo/packages/contracts-router/ \ No newline at end of file +@nomad-xyz/=lib/monorepo/packages/ +@nomad-xyz/contracts-router/=lib/monorepo/packages/contracts-router/ +@openzeppelin/=lib/monorepo/node_modules/@openzeppelin/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ +@summa-tx/=lib/monorepo/node_modules/@summa-tx/ +ds-test/=lib/forge-std/lib/ds-test/src/ +forge-std/=lib/forge-std/src/ +monorepo/=lib/monorepo/ +openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ \ No newline at end of file diff --git a/src/Router.sol b/src/Router.sol index 96a3f5b3..277a0500 100644 --- a/src/Router.sol +++ b/src/Router.sol @@ -2,9 +2,9 @@ pragma solidity ^0.7.6; import "forge-std/Test.sol"; -import {Router} from "@nomad-xyz/contracts-router/contracts/Router.sol"; +// import {Router} from "@nomad-xyz/contracts-router/contracts/Router.sol"; -contract ConnectorRouter is Router, Test { +contract ConnectorRouter is Test { uint32 immutable CENTRIFUGE_CHAIN_DOMAIN = 3000; function updateInvestOrder( @@ -18,11 +18,11 @@ contract ConnectorRouter is Router, Test { return; } - function send(bytes memory message) internal { - (_home()).dispatch( - CENTRIFUGE_CHAIN_DOMAIN, - _mustHaveRemote(CENTRIFUGE_CHAIN_DOMAIN), - message - ); - } + // function send(bytes memory message) internal { + // (_home()).dispatch( + // CENTRIFUGE_CHAIN_DOMAIN, + // _mustHaveRemote(CENTRIFUGE_CHAIN_DOMAIN), + // message + // ); + // } } From ce93bb5aea5950f0755a585d08a2925307c89da3 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Thu, 16 Jun 2022 13:58:02 +0200 Subject: [PATCH 08/30] Changes --- lib/forge-std | 2 +- lib/monorepo | 2 +- lib/openzeppelin-contracts-upgradeable | 2 +- remappings.txt | 6 +-- script/Connector.s.sol | 2 +- src/Connector.sol | 53 ++++++++++++++++++++++++-- src/Router.sol | 20 +++++----- test/Connector.t.sol | 2 +- test/MockHomeRouter.sol | 6 +++ 9 files changed, 72 insertions(+), 23 deletions(-) create mode 100644 test/MockHomeRouter.sol diff --git a/lib/forge-std b/lib/forge-std index e0393ac5..0d0485bd 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit e0393ac558fa62731e7890677c5f0559a06e39c2 +Subproject commit 0d0485bdea9f9455bd684acb0ba88548a104d99b diff --git a/lib/monorepo b/lib/monorepo index 09a9c247..df21c2d1 160000 --- a/lib/monorepo +++ b/lib/monorepo @@ -1 +1 @@ -Subproject commit 09a9c24728920b66a9013bb2e319b2c736991dad +Subproject commit df21c2d16539ad98be062db43e95a0e7341a2afc diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable index d86d4540..f5819bc4 160000 --- a/lib/openzeppelin-contracts-upgradeable +++ b/lib/openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit d86d45402e060579c35cf4557c2e54845a798519 +Subproject commit f5819bc47bdf0c049f9b1c2eed6b940cbff7ada2 diff --git a/remappings.txt b/remappings.txt index 294526cd..ad990828 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,9 +1,7 @@ @nomad-xyz/=lib/monorepo/packages/ -@nomad-xyz/contracts-router/=lib/monorepo/packages/contracts-router/ -@openzeppelin/=lib/monorepo/node_modules/@openzeppelin/ -@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ @summa-tx/=lib/monorepo/node_modules/@summa-tx/ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ monorepo/=lib/monorepo/ -openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ \ No newline at end of file +openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/ +@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/ \ No newline at end of file diff --git a/script/Connector.s.sol b/script/Connector.s.sol index 6e93289c..0ffe0f3f 100644 --- a/script/Connector.s.sol +++ b/script/Connector.s.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.7.6; import "forge-std/Script.sol"; diff --git a/src/Connector.sol b/src/Connector.sol index 3b741785..cc12df2d 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -1,21 +1,66 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.7.6; +pragma abicoder v2; interface RouterLike { - function updateInvestOrder(uint poolId, uint[] calldata trancheId, uint amount) external; + function updateInvestOrder(uint poolId, string calldata trancheId, uint amount) external; + function updateRedeemOrder(uint poolId, string calldata trancheId, uint amount) external; } contract CentrifugeConnector { RouterLike public immutable router; + struct Tranche { + uint latestPrice; // [ray] + address token; + } + + struct Pool { + uint poolId; + mapping (string => Tranche) tranches; + } + + mapping (uint => Pool) public pools; + constructor(address router_) { router = RouterLike(router_); } - function updateInvestOrder(uint poolId, uint[] calldata trancheId, uint amount) external { - // TODO: check pool_id is valid, check tranche_id is valid, transfer amount of pool currency to self + modifier onlyRouter { + require(msg.sender == address(router)); + _; + } + + /** Investor interactions **/ + function updateInvestOrder(uint poolId, string calldata trancheId, uint amount) external { + require(pools[poolId].poolId != 0, "unknown-pool"); + require(pools[poolId].tranches[trancheId].latestPrice != 0, "unknown-tranche"); + // TODO: check msg.sender is a member of the token + router.updateInvestOrder(poolId, trancheId, amount); } + function updateRedeemOrder(uint poolId, string calldata trancheId, uint amount) external { } + + /** Internal **/ + function addPool(uint poolId, string[] calldata trancheIds) public onlyRouter { + Pool storage pool = pools[poolId]; + pool.poolId = poolId; + + for (uint i = 0; i < trancheIds.length; i++) { + this.addTranche(poolId, trancheIds[i]); + } + } + + function addTranche(uint poolId, string calldata trancheId) public onlyRouter { + // Deploy restricted token + // Storage in tranche struct + + } + + function removeTranche(uint poolId, string calldata trancheId) public onlyRouter { } + function updateTokenPrice(uint poolId, string calldata trancheId, uint price) public onlyRouter { } + function updateMember(uint poolId, string calldata trancheId, address user, uint validUntil) public onlyRouter { } + } diff --git a/src/Router.sol b/src/Router.sol index 277a0500..d23c9769 100644 --- a/src/Router.sol +++ b/src/Router.sol @@ -1,10 +1,10 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.7.6; import "forge-std/Test.sol"; -// import {Router} from "@nomad-xyz/contracts-router/contracts/Router.sol"; +import {Router} from "@nomad-xyz/contracts-router/contracts/Router.sol"; -contract ConnectorRouter is Test { +contract ConnectorRouter is Router, Test { uint32 immutable CENTRIFUGE_CHAIN_DOMAIN = 3000; function updateInvestOrder( @@ -18,11 +18,11 @@ contract ConnectorRouter is Test { return; } - // function send(bytes memory message) internal { - // (_home()).dispatch( - // CENTRIFUGE_CHAIN_DOMAIN, - // _mustHaveRemote(CENTRIFUGE_CHAIN_DOMAIN), - // message - // ); - // } + function send(bytes memory message) internal { + (_home()).dispatch( + CENTRIFUGE_CHAIN_DOMAIN, + _mustHaveRemote(CENTRIFUGE_CHAIN_DOMAIN), + message + ); + } } diff --git a/test/Connector.t.sol b/test/Connector.t.sol index b82cb4be..bc5ec7b4 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.7.6; import "forge-std/Test.sol"; diff --git a/test/MockHomeRouter.sol b/test/MockHomeRouter.sol new file mode 100644 index 00000000..80c43a02 --- /dev/null +++ b/test/MockHomeRouter.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.7.6; + +contract MockHomeRouter { + // TOOD: act as the Router on Centrifuge Chain +} From df45a2f17478a33c2ab69509a755a9c0fc51a421 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Thu, 16 Jun 2022 13:59:51 +0200 Subject: [PATCH 09/30] Remove dep --- .gitmodules | 3 --- lib/openzeppelin-contracts-upgradeable | 1 - 2 files changed, 4 deletions(-) delete mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index 28158a3a..4d20d9d9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,3 @@ [submodule "lib/monorepo"] path = lib/monorepo url = https://github.com/nomad-xyz/monorepo -[submodule "lib/openzeppelin-contracts-upgradeable"] - path = lib/openzeppelin-contracts-upgradeable - url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable deleted file mode 160000 index f5819bc4..00000000 --- a/lib/openzeppelin-contracts-upgradeable +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f5819bc47bdf0c049f9b1c2eed6b940cbff7ada2 From 2887e023b5f8942deefc8f5463ad12b684732674 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Thu, 16 Jun 2022 13:59:59 +0200 Subject: [PATCH 10/30] forge install: openzeppelin-contracts-upgradeable v3.4.2-solc-0.7 --- .gitmodules | 3 +++ lib/openzeppelin-contracts-upgradeable | 1 + 2 files changed, 4 insertions(+) create mode 160000 lib/openzeppelin-contracts-upgradeable diff --git a/.gitmodules b/.gitmodules index 4d20d9d9..28158a3a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "lib/monorepo"] path = lib/monorepo url = https://github.com/nomad-xyz/monorepo +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 00000000..e0683346 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit e0683346f70db930bd39551046b12449ea68f2dc From 927c5dad501dc129d89da494a13b07186269988e Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Thu, 16 Jun 2022 14:04:33 +0200 Subject: [PATCH 11/30] Fix compilation --- src/Router.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Router.sol b/src/Router.sol index d23c9769..9a6c8125 100644 --- a/src/Router.sol +++ b/src/Router.sol @@ -25,4 +25,14 @@ contract ConnectorRouter is Router, Test { message ); } + + function handle( + uint32, + uint32, + bytes32, + bytes memory _message + ) external override { + console.log("handle called"); + console.log(string(_message)); + } } From efda62e4734c3cff90eb3a6d7e0fae7d5fd5223f Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Fri, 17 Jun 2022 14:43:06 +0200 Subject: [PATCH 12/30] Make basic routing work --- script/Connector.s.sol | 4 +- src/Connector.sol | 163 ++++++++++++++++++++++++++--------- src/Messages.sol | 26 ++++++ src/Router.sol | 38 -------- src/routers/nomad/Router.sol | 65 ++++++++++++++ test/Connector.t.sol | 23 ++++- test/MockHomeConnector.sol | 31 +++++++ test/MockHomeRouter.sol | 6 -- 8 files changed, 264 insertions(+), 92 deletions(-) create mode 100644 src/Messages.sol delete mode 100644 src/Router.sol create mode 100644 src/routers/nomad/Router.sol create mode 100644 test/MockHomeConnector.sol delete mode 100644 test/MockHomeRouter.sol diff --git a/script/Connector.s.sol b/script/Connector.s.sol index 0ffe0f3f..758c0765 100644 --- a/script/Connector.s.sol +++ b/script/Connector.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.7.6; import "forge-std/Script.sol"; -import {ConnectorRouter} from "src/Router.sol"; +import {ConnectorRouter} from "src/routers/nomad/Router.sol"; import {CentrifugeConnector} from "src/Connector.sol"; contract ConnectorScript is Script { @@ -11,7 +11,7 @@ contract ConnectorScript is Script { function run() public { vm.broadcast(); - ConnectorRouter router = new ConnectorRouter(); + ConnectorRouter router = new ConnectorRouter(address(0)); new CentrifugeConnector(address(router)); } } diff --git a/src/Connector.sol b/src/Connector.sol index cc12df2d..b22d2223 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -2,65 +2,142 @@ pragma solidity ^0.7.6; pragma abicoder v2; +import "forge-std/Test.sol"; + interface RouterLike { - function updateInvestOrder(uint poolId, string calldata trancheId, uint amount) external; - function updateRedeemOrder(uint poolId, string calldata trancheId, uint amount) external; + function updateInvestOrder( + uint256 poolId, + string calldata trancheId, + uint256 amount + ) external; + + function updateRedeemOrder( + uint256 poolId, + string calldata trancheId, + uint256 amount + ) external; } -contract CentrifugeConnector { +contract CentrifugeConnector is Test { - RouterLike public immutable router; + RouterLike public router; - struct Tranche { - uint latestPrice; // [ray] - address token; - } + // --- Storage --- + mapping(address => uint256) public wards; - struct Pool { - uint poolId; - mapping (string => Tranche) tranches; - } - - mapping (uint => Pool) public pools; + struct Tranche { + uint256 latestPrice; // [ray] + address token; + } - constructor(address router_) { - router = RouterLike(router_); - } + struct Pool { + uint256 poolId; + mapping(string => Tranche) tranches; + } - modifier onlyRouter { - require(msg.sender == address(router)); - _; - } + mapping(uint256 => Pool) public pools; - /** Investor interactions **/ - function updateInvestOrder(uint poolId, string calldata trancheId, uint amount) external { - require(pools[poolId].poolId != 0, "unknown-pool"); - require(pools[poolId].tranches[trancheId].latestPrice != 0, "unknown-tranche"); - // TODO: check msg.sender is a member of the token + // --- Events --- + event Rely(address indexed user); + event Deny(address indexed user); + event File(bytes32 indexed what, address data); + event PoolAdded(uint256 indexed poolId); - router.updateInvestOrder(poolId, trancheId, amount); - } + constructor(address router_) { + router = RouterLike(router_); + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + modifier auth { + require(wards[msg.sender] == 1, "CentrifugeConnector/not-authorized"); + _; + } - function updateRedeemOrder(uint poolId, string calldata trancheId, uint amount) external { } + modifier onlyRouter() { + require( + msg.sender == address(router), + "CentrifugeConnector/not-authorized" + ); + _; + } - /** Internal **/ - function addPool(uint poolId, string[] calldata trancheIds) public onlyRouter { - Pool storage pool = pools[poolId]; - pool.poolId = poolId; + // --- Administration --- + function rely(address usr) external auth { + wards[usr] = 1; + emit Rely(usr); + } - for (uint i = 0; i < trancheIds.length; i++) { - this.addTranche(poolId, trancheIds[i]); + function deny(address usr) external auth { + wards[usr] = 0; + emit Deny(usr); } - } - function addTranche(uint poolId, string calldata trancheId) public onlyRouter { - // Deploy restricted token - // Storage in tranche struct + function file(bytes32 what, address data) external auth { + if (what == "router") router = RouterLike(data); + else revert("CentrifugeConnector/file-unrecognized-param"); + emit File(what, data); + } - } + // --- Investor interactions --- + function updateInvestOrder( + uint256 poolId, + string calldata trancheId, + uint256 amount + ) external { + require(pools[poolId].poolId != 0, "CentrifugeConnector/unknown-pool"); + require( + pools[poolId].tranches[trancheId].latestPrice != 0, + "CentrifugeConnector/unknown-tranche" + ); + // TODO: check msg.sender is a member of the token + + router.updateInvestOrder(poolId, trancheId, amount); + } + + function updateRedeemOrder( + uint256 poolId, + string calldata trancheId, + uint256 amount + ) external {} + + // --- Internal --- + // TOOD: add string[] calldata trancheIds + function addPool(uint256 poolId) public onlyRouter { + console.log("Adding a pool in Connector"); + Pool storage pool = pools[poolId]; + pool.poolId = poolId; + + // for (uint i = 0; i < trancheIds.length; i++) { + // this.addTranche(poolId, trancheIds[i]); + // } - function removeTranche(uint poolId, string calldata trancheId) public onlyRouter { } - function updateTokenPrice(uint poolId, string calldata trancheId, uint price) public onlyRouter { } - function updateMember(uint poolId, string calldata trancheId, address user, uint validUntil) public onlyRouter { } + emit PoolAdded(poolId); + } + + function addTranche(uint256 poolId, string calldata trancheId) + public + onlyRouter + { + // Deploy restricted token + // Storage in tranche struct + } + function removeTranche(uint256 poolId, string calldata trancheId) + public + onlyRouter + {} + + function updateTokenPrice( + uint256 poolId, + string calldata trancheId, + uint256 price + ) public onlyRouter {} + + function updateMember( + uint256 poolId, + string calldata trancheId, + address user, + uint256 validUntil + ) public onlyRouter {} } diff --git a/src/Messages.sol b/src/Messages.sol new file mode 100644 index 00000000..9bf680dd --- /dev/null +++ b/src/Messages.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.7.6; + +import "@summa-tx/memview-sol/contracts/TypedMemView.sol"; + +library ConnectorMessages { + using TypedMemView for bytes; + using TypedMemView for bytes29; + + enum Types { + Invalid, + AddPool + } + + function formatAddPool(uint256 poolId) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Types.AddPool), poolId); + } + + function messageType(bytes29 _view) internal pure returns (Types _type) { + _type = Types(uint8(_view.typeOf())); + } + + function isAddPool(bytes29 _view) internal pure returns (bool) { + return messageType(_view) == Types.AddPool; + } +} \ No newline at end of file diff --git a/src/Router.sol b/src/Router.sol deleted file mode 100644 index 9a6c8125..00000000 --- a/src/Router.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.7.6; - -import "forge-std/Test.sol"; -import {Router} from "@nomad-xyz/contracts-router/contracts/Router.sol"; - -contract ConnectorRouter is Router, Test { - uint32 immutable CENTRIFUGE_CHAIN_DOMAIN = 3000; - - function updateInvestOrder( - uint256 poolId, - uint256[] calldata trancheId, - uint256 amount - ) external { - console.log(poolId); - console.log(amount); - // TODO: send message to Nomad Home contract - return; - } - - function send(bytes memory message) internal { - (_home()).dispatch( - CENTRIFUGE_CHAIN_DOMAIN, - _mustHaveRemote(CENTRIFUGE_CHAIN_DOMAIN), - message - ); - } - - function handle( - uint32, - uint32, - bytes32, - bytes memory _message - ) external override { - console.log("handle called"); - console.log(string(_message)); - } -} diff --git a/src/routers/nomad/Router.sol b/src/routers/nomad/Router.sol new file mode 100644 index 00000000..698353f7 --- /dev/null +++ b/src/routers/nomad/Router.sol @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.7.6; +pragma abicoder v2; + +import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol"; +import {Router} from "@nomad-xyz/contracts-router/contracts/Router.sol"; +import {ConnectorMessages} from "../..//Messages.sol"; +import "forge-std/Test.sol"; + +interface ConnectorLike { + function addPool(uint poolId) external; +} + +contract ConnectorRouter is Router, Test { + using TypedMemView for bytes; + using TypedMemView for bytes29; + using ConnectorMessages for bytes29; + + ConnectorLike public immutable connector; + + uint32 immutable CENTRIFUGE_CHAIN_DOMAIN = 3000; + + enum Types { + AddPool + } + + constructor(address connector_) { + connector = ConnectorLike(connector_); + } + + function updateInvestOrder( + uint256 poolId, + uint256[] calldata trancheId, + uint256 amount + ) external { + console.log(poolId); + console.log(amount); + // TODO: send message to Nomad Home contract by calling send() + return; + } + + function send(bytes memory message) internal { + (_home()).dispatch( + CENTRIFUGE_CHAIN_DOMAIN, + _mustHaveRemote(CENTRIFUGE_CHAIN_DOMAIN), + message + ); + } + + function handle( + uint32 _origin, + uint32 _nonce, + bytes32 _sender, + bytes memory _message + ) external override { + bytes29 _msg = _message.ref(0); + if (ConnectorMessages.isAddPool(_msg) == true) { + console.log("yes"); + } else { + console.log("no"); + } + // TODO: actually decode + connector.addPool(1); + } +} diff --git a/test/Connector.t.sol b/test/Connector.t.sol index bc5ec7b4..d3bb6534 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -1,12 +1,29 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.7.6; +pragma abicoder v2; import "forge-std/Test.sol"; +import "src/Connector.sol"; +import "src/routers/nomad/Router.sol"; +import "./MockHomeConnector.sol"; contract ConnectorTest is Test { - function setUp() public {} - function testExample() public { - assertTrue(true); + CentrifugeConnector bridgedConnector; + ConnectorRouter bridgedRouter; + MockHomeConnector homeConnector; + + function setUp() public { + bridgedConnector = new CentrifugeConnector(address(this)); + bridgedRouter = new ConnectorRouter(address(bridgedConnector)); + bridgedConnector.file("router", address(bridgedRouter)); + + homeConnector = new MockHomeConnector(address(bridgedRouter)); + } + + function testAddingPool(uint poolId) public { + homeConnector.addPool(poolId); + (uint actualPoolId) = bridgedConnector.pools(poolId); + assertEq(actualPoolId, poolId); } } diff --git a/test/MockHomeConnector.sol b/test/MockHomeConnector.sol new file mode 100644 index 00000000..580c553e --- /dev/null +++ b/test/MockHomeConnector.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.7.6; +pragma abicoder v2; + +import {IMessageRecipient} from "@nomad-xyz/contracts-core/contracts/interfaces/IMessageRecipient.sol"; +import {ConnectorMessages} from "src/Messages.sol"; +import "forge-std/Test.sol"; + +contract MockHomeConnector is Test { + using ConnectorMessages for bytes29; + + IMessageRecipient public immutable home; + + uint32 immutable CENTRIFUGE_CHAIN_DOMAIN = 3000; + + uint32 immutable NONCE = 1; + + enum Types { + AddPool + } + + constructor(address home_) { + home = IMessageRecipient(home_); + } + + function addPool(uint poolId) public returns (bool) { + bytes memory _message = ConnectorMessages.formatAddPool(poolId); + home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); + return true; + } +} diff --git a/test/MockHomeRouter.sol b/test/MockHomeRouter.sol deleted file mode 100644 index 80c43a02..00000000 --- a/test/MockHomeRouter.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity ^0.7.6; - -contract MockHomeRouter { - // TOOD: act as the Router on Centrifuge Chain -} From cbaf4e301f5f1809051f9090c1d6a05c62b6481a Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Fri, 17 Jun 2022 15:42:11 +0200 Subject: [PATCH 13/30] Set up token factory --- script/Connector.s.sol | 10 +++- src/Connector.sol | 15 ++++- src/token/erc20.sol | 125 +++++++++++++++++++++++++++++++++++++++ src/token/factory.sol | 17 ++++++ src/token/memberlist.sol | 51 ++++++++++++++++ src/token/restricted.sol | 35 +++++++++++ test/Connector.t.sol | 5 +- 7 files changed, 251 insertions(+), 7 deletions(-) create mode 100644 src/token/erc20.sol create mode 100644 src/token/factory.sol create mode 100644 src/token/memberlist.sol create mode 100644 src/token/restricted.sol diff --git a/script/Connector.s.sol b/script/Connector.s.sol index 758c0765..87511629 100644 --- a/script/Connector.s.sol +++ b/script/Connector.s.sol @@ -3,15 +3,19 @@ pragma solidity ^0.7.6; import "forge-std/Script.sol"; -import {ConnectorRouter} from "src/routers/nomad/Router.sol"; -import {CentrifugeConnector} from "src/Connector.sol"; +import { ConnectorRouter } from "src/routers/nomad/Router.sol"; +import { CentrifugeConnector } from "src/Connector.sol"; +import { RestrictedTokenFactory } from "src/token/factory.sol"; contract ConnectorScript is Script { function setUp() public {} function run() public { vm.broadcast(); + + address tokenFactory_ = address(new RestrictedTokenFactory()); ConnectorRouter router = new ConnectorRouter(address(0)); - new CentrifugeConnector(address(router)); + + new CentrifugeConnector(address(router), tokenFactory_); } } diff --git a/src/Connector.sol b/src/Connector.sol index b22d2223..b84f66ce 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -2,6 +2,9 @@ pragma solidity ^0.7.6; pragma abicoder v2; +import { RestrictedTokenFactoryLike } from "./token/factory.sol"; +import { RestrictedTokenLike } from "./token/restricted.sol"; +import { MemberlistLike } from "./token/memberlist.sol"; import "forge-std/Test.sol"; interface RouterLike { @@ -21,6 +24,7 @@ interface RouterLike { contract CentrifugeConnector is Test { RouterLike public router; + RestrictedTokenFactoryLike public immutable tokenFactory; // --- Storage --- mapping(address => uint256) public wards; @@ -43,8 +47,9 @@ contract CentrifugeConnector is Test { event File(bytes32 indexed what, address data); event PoolAdded(uint256 indexed poolId); - constructor(address router_) { + constructor(address router_, address tokenFactory_) { router = RouterLike(router_); + tokenFactory = RestrictedTokenFactoryLike(tokenFactory_); wards[msg.sender] = 1; emit Rely(msg.sender); } @@ -120,7 +125,7 @@ contract CentrifugeConnector is Test { onlyRouter { // Deploy restricted token - // Storage in tranche struct + // tranche.token = tokenFactory.newRestrictedToken(symbol, name); } function removeTranche(uint256 poolId, string calldata trancheId) @@ -139,5 +144,9 @@ contract CentrifugeConnector is Test { string calldata trancheId, address user, uint256 validUntil - ) public onlyRouter {} + ) public onlyRouter { + RestrictedTokenLike token = RestrictedTokenLike(pools[poolId].tranches[trancheId].token); + MemberlistLike memberlist = MemberlistLike(token.memberlist()); + memberlist.updateMember(user, validUntil); + } } diff --git a/src/token/erc20.sol b/src/token/erc20.sol new file mode 100644 index 00000000..28272c14 --- /dev/null +++ b/src/token/erc20.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Copyright (C) 2017, 2018, 2019 dbrock, rain, mrchico, lucasvo +pragma solidity >=0.7.0; + +contract ERC20 { + // --- Auth --- + mapping (address => uint) public wards; + function rely(address usr) public auth { wards[usr] = 1; } + function deny(address usr) public auth { wards[usr] = 0; } + modifier auth { require(wards[msg.sender] == 1); _; } + + // --- ERC20 Data --- + uint8 public constant decimals = 18; + string public name; + string public symbol; + string public constant version = "1"; + uint256 public totalSupply; + + bytes32 public DOMAIN_SEPARATOR; + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"); + bytes32 public constant PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; + mapping(address => uint) public nonces; + + mapping (address => uint) public balanceOf; + mapping (address => mapping (address => uint)) public allowance; + + event Approval(address indexed src, address indexed usr, uint wad); + event Transfer(address indexed src, address indexed dst, uint wad); + + // --- Math --- + function safeAdd_(uint x, uint y) internal pure returns (uint z) { + require((z = x + y) >= x, "math-add-overflow"); + } + function safeSub_(uint x, uint y) internal pure returns (uint z) { + require((z = x - y) <= x, "math-sub-underflow"); + } + + constructor(string memory symbol_, string memory name_) { + wards[msg.sender] = 1; + symbol = symbol_; + name = name_; + + uint chainId; + assembly { + chainId := chainid() + } + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'), + keccak256(bytes(name)), + keccak256(bytes(version)), + chainId, + address(this) + ) + ); + } + + // --- ERC20 --- + function transfer(address dst, uint wad) external returns (bool) { + return transferFrom(msg.sender, dst, wad); + } + function transferFrom(address src, address dst, uint wad) + public virtual returns (bool) + { + require(balanceOf[src] >= wad, "cent/insufficient-balance"); + if (src != msg.sender && allowance[src][msg.sender] != type(uint256).max) { + require(allowance[src][msg.sender] >= wad, "cent/insufficient-allowance"); + allowance[src][msg.sender] = safeSub_(allowance[src][msg.sender], wad); + } + balanceOf[src] = safeSub_(balanceOf[src], wad); + balanceOf[dst] = safeAdd_(balanceOf[dst], wad); + emit Transfer(src, dst, wad); + return true; + } + function mint(address usr, uint wad) external virtual auth { + balanceOf[usr] = safeAdd_(balanceOf[usr], wad); + totalSupply = safeAdd_(totalSupply, wad); + emit Transfer(address(0), usr, wad); + } + function burn(address usr, uint wad) public { + require(balanceOf[usr] >= wad, "cent/insufficient-balance"); + if (usr != msg.sender && allowance[usr][msg.sender] != type(uint256).max) { + require(allowance[usr][msg.sender] >= wad, "cent/insufficient-allowance"); + allowance[usr][msg.sender] = safeSub_(allowance[usr][msg.sender], wad); + } + balanceOf[usr] = safeSub_(balanceOf[usr], wad); + totalSupply = safeSub_(totalSupply, wad); + emit Transfer(usr, address(0), wad); + } + function approve(address usr, uint wad) external returns (bool) { + allowance[msg.sender][usr] = wad; + emit Approval(msg.sender, usr, wad); + return true; + } + + // --- Alias --- + function push(address usr, uint wad) external { + transferFrom(msg.sender, usr, wad); + } + function pull(address usr, uint wad) external { + transferFrom(usr, msg.sender, wad); + } + function move(address src, address dst, uint wad) external { + transferFrom(src, dst, wad); + } + function burnFrom(address usr, uint wad) external { + burn(usr, wad); + } + + // --- Approve by signature --- + function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external { + require(deadline >= block.timestamp, 'cent/past-deadline'); + bytes32 digest = keccak256( + abi.encodePacked( + '\x19\x01', + DOMAIN_SEPARATOR, + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) + ) + ); + address recoveredAddress = ecrecover(digest, v, r, s); + require(recoveredAddress != address(0) && recoveredAddress == owner, 'cent-erc20/invalid-sig'); + allowance[owner][spender] = value; + emit Approval(owner, spender, value); + } +} \ No newline at end of file diff --git a/src/token/factory.sol b/src/token/factory.sol new file mode 100644 index 00000000..2b26798a --- /dev/null +++ b/src/token/factory.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.7.6; + +import { RestrictedToken } from "./restricted.sol"; + +interface RestrictedTokenFactoryLike { + function newRestrictedToken(string calldata, string calldata) external returns (address); +} + +contract RestrictedTokenFactory { + function newRestrictedToken(string memory symbol, string memory name) public returns (address) { + RestrictedToken token = new RestrictedToken(symbol, name); + token.rely(msg.sender); + token.deny(address(this)); + return address(token); + } +} \ No newline at end of file diff --git a/src/token/memberlist.sol b/src/token/memberlist.sol new file mode 100644 index 00000000..126f0b84 --- /dev/null +++ b/src/token/memberlist.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.7.6; + +interface MemberlistLike { + function updateMember(address usr, uint validUntil) external; +} + +contract Memberlist { + + mapping(address => uint256) public wards; + + uint constant minimumDelay = 7 days; + + modifier auth { + require(wards[msg.sender] == 1, "not-authorized"); + _; + } + + // --- Math --- + function safeAdd(uint x, uint y) internal pure returns (uint z) { + require((z = x + y) >= x, "math-add-overflow"); + } + + // -- Members-- + mapping (address => uint) public members; + function updateMember(address usr, uint validUntil) public auth { + require((safeAdd(block.timestamp, minimumDelay)) < validUntil); + members[usr] = validUntil; + } + + function updateMembers(address[] memory users, uint validUntil) public auth { + for (uint i = 0; i < users.length; i++) { + updateMember(users[i], validUntil); + } + } + + constructor() { + wards[msg.sender] = 1; + } + + function member(address usr) public view { + require((members[usr] >= block.timestamp), "not-allowed-to-hold-token"); + } + + function hasMember(address usr) public view returns (bool) { + if (members[usr] >= block.timestamp) { + return true; + } + return false; + } +} \ No newline at end of file diff --git a/src/token/restricted.sol b/src/token/restricted.sol new file mode 100644 index 00000000..35a90cdb --- /dev/null +++ b/src/token/restricted.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.7.6; + +import "./erc20.sol"; + +interface MemberlistLike { + function hasMember(address) external view returns (bool); + function member(address) external; +} + +interface RestrictedTokenLike { + function memberlist() external view returns (address); +} + +// Only mebmber with a valid (not expired) membership should be allowed to receive tokens +contract RestrictedToken is ERC20 { + + MemberlistLike public memberlist; + modifier checkMember(address usr) { memberlist.member(usr); _; } + + function hasMember(address usr) public view returns (bool) { + return memberlist.hasMember(usr); + } + + constructor(string memory symbol_, string memory name_) ERC20(symbol_, name_) {} + + function depend(bytes32 contractName, address addr) public auth { + if (contractName == "memberlist") { memberlist = MemberlistLike(addr); } + else revert(); + } + + function transferFrom(address from, address to, uint wad) checkMember(to) public override returns (bool) { + return super.transferFrom(from, to, wad); + } +} diff --git a/test/Connector.t.sol b/test/Connector.t.sol index d3bb6534..529253ae 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -4,6 +4,7 @@ pragma abicoder v2; import "forge-std/Test.sol"; import "src/Connector.sol"; +import { RestrictedTokenFactory } from "src/token/factory.sol"; import "src/routers/nomad/Router.sol"; import "./MockHomeConnector.sol"; @@ -14,7 +15,9 @@ contract ConnectorTest is Test { MockHomeConnector homeConnector; function setUp() public { - bridgedConnector = new CentrifugeConnector(address(this)); + address tokenFactory_ = address(new RestrictedTokenFactory()); + + bridgedConnector = new CentrifugeConnector(address(this), tokenFactory_); bridgedRouter = new ConnectorRouter(address(bridgedConnector)); bridgedConnector.file("router", address(bridgedRouter)); From f72cedf98f317b38477adfb52a8a2f55afb4132c Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Mon, 20 Jun 2022 21:26:13 +0200 Subject: [PATCH 14/30] Set up more methods and tests --- script/Connector.s.sol | 3 +- src/Connector.sol | 68 ++++++++------------------- src/Messages.sol | 10 ++-- src/routers/nomad/Router.sol | 4 -- test/Connector.t.sol | 29 +++++++++--- test/{ => mock}/MockHomeConnector.sol | 0 6 files changed, 48 insertions(+), 66 deletions(-) rename test/{ => mock}/MockHomeConnector.sol (100%) diff --git a/script/Connector.s.sol b/script/Connector.s.sol index 87511629..19f0c0df 100644 --- a/script/Connector.s.sol +++ b/script/Connector.s.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.7.6; -import "forge-std/Script.sol"; - import { ConnectorRouter } from "src/routers/nomad/Router.sol"; import { CentrifugeConnector } from "src/Connector.sol"; import { RestrictedTokenFactory } from "src/token/factory.sol"; +import "forge-std/Script.sol"; contract ConnectorScript is Script { function setUp() public {} diff --git a/src/Connector.sol b/src/Connector.sol index b84f66ce..d02aa125 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -8,17 +8,6 @@ import { MemberlistLike } from "./token/memberlist.sol"; import "forge-std/Test.sol"; interface RouterLike { - function updateInvestOrder( - uint256 poolId, - string calldata trancheId, - uint256 amount - ) external; - - function updateRedeemOrder( - uint256 poolId, - string calldata trancheId, - uint256 amount - ) external; } contract CentrifugeConnector is Test { @@ -46,6 +35,7 @@ contract CentrifugeConnector is Test { event Deny(address indexed user); event File(bytes32 indexed what, address data); event PoolAdded(uint256 indexed poolId); + event TrancheAdded(uint256 indexed poolId, string indexed trancheId, address indexed token); constructor(address router_, address tokenFactory_) { router = RouterLike(router_); @@ -60,10 +50,7 @@ contract CentrifugeConnector is Test { } modifier onlyRouter() { - require( - msg.sender == address(router), - "CentrifugeConnector/not-authorized" - ); + require(msg.sender == address(router), "CentrifugeConnector/not-the-router"); _; } @@ -84,39 +71,11 @@ contract CentrifugeConnector is Test { emit File(what, data); } - // --- Investor interactions --- - function updateInvestOrder( - uint256 poolId, - string calldata trancheId, - uint256 amount - ) external { - require(pools[poolId].poolId != 0, "CentrifugeConnector/unknown-pool"); - require( - pools[poolId].tranches[trancheId].latestPrice != 0, - "CentrifugeConnector/unknown-tranche" - ); - // TODO: check msg.sender is a member of the token - - router.updateInvestOrder(poolId, trancheId, amount); - } - - function updateRedeemOrder( - uint256 poolId, - string calldata trancheId, - uint256 amount - ) external {} - // --- Internal --- - // TOOD: add string[] calldata trancheIds function addPool(uint256 poolId) public onlyRouter { console.log("Adding a pool in Connector"); Pool storage pool = pools[poolId]; pool.poolId = poolId; - - // for (uint i = 0; i < trancheIds.length; i++) { - // this.addTranche(poolId, trancheIds[i]); - // } - emit PoolAdded(poolId); } @@ -124,15 +83,15 @@ contract CentrifugeConnector is Test { public onlyRouter { + Pool storage pool = pools[poolId]; + Tranche storage tranche = pool.tranches[trancheId]; + // Deploy restricted token - // tranche.token = tokenFactory.newRestrictedToken(symbol, name); + // TODO: set actual symbol and name + tranche.token = tokenFactory.newRestrictedToken("SYMBOL", "Name"); + emit TrancheAdded(poolId, trancheId, tranche.token); } - function removeTranche(uint256 poolId, string calldata trancheId) - public - onlyRouter - {} - function updateTokenPrice( uint256 poolId, string calldata trancheId, @@ -149,4 +108,15 @@ contract CentrifugeConnector is Test { MemberlistLike memberlist = MemberlistLike(token.memberlist()); memberlist.updateMember(user, validUntil); } + + function transferTo( + uint256 poolId, + string calldata trancheId, + address user, + uint256 amount + ) public onlyRouter { + RestrictedTokenLike token = RestrictedTokenLike(pools[poolId].tranches[trancheId].token); + token.mint(user, amount); + } + } diff --git a/src/Messages.sol b/src/Messages.sol index 9bf680dd..76eba593 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -7,20 +7,20 @@ library ConnectorMessages { using TypedMemView for bytes; using TypedMemView for bytes29; - enum Types { + enum Calls { Invalid, AddPool } function formatAddPool(uint256 poolId) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Types.AddPool), poolId); + return abi.encodePacked(uint8(Calls.AddPool), poolId); } - function messageType(bytes29 _view) internal pure returns (Types _type) { - _type = Types(uint8(_view.typeOf())); + function messageType(bytes29 _view) internal pure returns (Calls _call) { + _call = Calls(uint8(_view.typeOf())); } function isAddPool(bytes29 _view) internal pure returns (bool) { - return messageType(_view) == Types.AddPool; + return messageType(_view) == Calls.AddPool; } } \ No newline at end of file diff --git a/src/routers/nomad/Router.sol b/src/routers/nomad/Router.sol index 698353f7..123487b4 100644 --- a/src/routers/nomad/Router.sol +++ b/src/routers/nomad/Router.sol @@ -20,10 +20,6 @@ contract ConnectorRouter is Router, Test { uint32 immutable CENTRIFUGE_CHAIN_DOMAIN = 3000; - enum Types { - AddPool - } - constructor(address connector_) { connector = ConnectorLike(connector_); } diff --git a/test/Connector.t.sol b/test/Connector.t.sol index 529253ae..b8cd88c7 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -2,11 +2,11 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import "forge-std/Test.sol"; -import "src/Connector.sol"; +import { CentrifugeConnector } from "src/Connector.sol"; import { RestrictedTokenFactory } from "src/token/factory.sol"; -import "src/routers/nomad/Router.sol"; -import "./MockHomeConnector.sol"; +import { MockHomeConnector } from "./mock/MockHomeConnector.sol"; +import { ConnectorRouter } from "src/routers/nomad/Router.sol"; +import "forge-std/Test.sol"; contract ConnectorTest is Test { @@ -24,9 +24,26 @@ contract ConnectorTest is Test { homeConnector = new MockHomeConnector(address(bridgedRouter)); } - function testAddingPool(uint poolId) public { + function testAddingPoolWorks(uint poolId) public { homeConnector.addPool(poolId); (uint actualPoolId) = bridgedConnector.pools(poolId); assertEq(actualPoolId, poolId); } -} + + function testAddingPoolAsNonRouterFails(uint poolId) public { } + function testAddingTranchesWorks(uint poolId, string memory trancheId) public { } + function testAddingTranchesAsNonRouterFails(uint poolId, string memory trancheId) public { } + function testUpdatingMemberWorks(uint poolId) public { } + function testUpdatingMemberAsNonRouterFails(uint poolId) public { } + function testUpdatingMemberForNonExistentPoolFails(uint poolId) public { } + function testUpdatingMemberForNonExistentTrancheFails(uint poolId) public { } + function testUpdatingTokenPriceWorks(uint poolId) public { } + function testUpdatingTokenPriceAsNonRouterFails(uint poolId) public { } + function testUpdatingTokenPriceForNonExistentPoolFails(uint poolId) public { } + function testUpdatingTokenPriceForNonExistentTrancheFails(uint poolId) public { } + function testTransferToWorks(uint poolId) public { } + function testTransferToAsNonRouterFails(uint poolId) public { } + function testTransferToForNonExistentPoolFails(uint poolId) public { } + function testTransferToForNonExistentTrancheFails(uint poolId) public { } + +} \ No newline at end of file diff --git a/test/MockHomeConnector.sol b/test/mock/MockHomeConnector.sol similarity index 100% rename from test/MockHomeConnector.sol rename to test/mock/MockHomeConnector.sol From 2af145a61587f2e8361a187714540e8fe893b790 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Mon, 20 Jun 2022 22:06:27 +0200 Subject: [PATCH 15/30] Fix message parsing --- src/Connector.sol | 15 ++++++------- src/Messages.sol | 24 ++++++++++++++------- src/routers/nomad/Router.sol | 11 +++++----- src/token/restricted.sol | 7 ++++++- test/Connector.t.sol | 37 +++++++++++++++++---------------- test/mock/MockHomeConnector.sol | 5 ++++- 6 files changed, 59 insertions(+), 40 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index d02aa125..017e7676 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -24,11 +24,11 @@ contract CentrifugeConnector is Test { } struct Pool { - uint256 poolId; + uint64 poolId; mapping(string => Tranche) tranches; } - mapping(uint256 => Pool) public pools; + mapping(uint64 => Pool) public pools; // --- Events --- event Rely(address indexed user); @@ -72,14 +72,14 @@ contract CentrifugeConnector is Test { } // --- Internal --- - function addPool(uint256 poolId) public onlyRouter { + function addPool(uint64 poolId) public onlyRouter { console.log("Adding a pool in Connector"); Pool storage pool = pools[poolId]; pool.poolId = poolId; emit PoolAdded(poolId); } - function addTranche(uint256 poolId, string calldata trancheId) + function addTranche(uint64 poolId, string calldata trancheId) public onlyRouter { @@ -93,13 +93,13 @@ contract CentrifugeConnector is Test { } function updateTokenPrice( - uint256 poolId, + uint64 poolId, string calldata trancheId, uint256 price ) public onlyRouter {} function updateMember( - uint256 poolId, + uint64 poolId, string calldata trancheId, address user, uint256 validUntil @@ -110,12 +110,13 @@ contract CentrifugeConnector is Test { } function transferTo( - uint256 poolId, + uint64 poolId, string calldata trancheId, address user, uint256 amount ) public onlyRouter { RestrictedTokenLike token = RestrictedTokenLike(pools[poolId].tranches[trancheId].token); + require(token.hasMember(user), "CentrifugeConnector/not-a-member"); token.mint(user, amount); } diff --git a/src/Messages.sol b/src/Messages.sol index 76eba593..97acea60 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -7,20 +7,28 @@ library ConnectorMessages { using TypedMemView for bytes; using TypedMemView for bytes29; - enum Calls { + enum Call { Invalid, - AddPool + AddPool, + AddTranche, + UpdateTokenPrice, + UpdateMember, + TransferTo } - function formatAddPool(uint256 poolId) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Calls.AddPool), poolId); + function formatAddPool(uint64 poolId) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.AddPool), poolId); } - function messageType(bytes29 _view) internal pure returns (Calls _call) { - _call = Calls(uint8(_view.typeOf())); + function messageType(bytes29 _msg) internal pure returns (Call _call) { + _call = Call(uint8(_msg.indexUint(0, 1))); } - function isAddPool(bytes29 _view) internal pure returns (bool) { - return messageType(_view) == Calls.AddPool; + function isAddPool(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.AddPool; + } + + function parseAddPool(bytes29 _msg) internal pure returns (uint64 poolId) { + return uint64(_msg.indexUint(1, 8)); } } \ No newline at end of file diff --git a/src/routers/nomad/Router.sol b/src/routers/nomad/Router.sol index 123487b4..305c1010 100644 --- a/src/routers/nomad/Router.sol +++ b/src/routers/nomad/Router.sol @@ -8,7 +8,7 @@ import {ConnectorMessages} from "../..//Messages.sol"; import "forge-std/Test.sol"; interface ConnectorLike { - function addPool(uint poolId) external; + function addPool(uint64 poolId) external; } contract ConnectorRouter is Router, Test { @@ -43,6 +43,7 @@ contract ConnectorRouter is Router, Test { ); } + // TODO: onlyReplica onlyRemoteRouter(_origin, _sender) function handle( uint32 _origin, uint32 _nonce, @@ -51,11 +52,11 @@ contract ConnectorRouter is Router, Test { ) external override { bytes29 _msg = _message.ref(0); if (ConnectorMessages.isAddPool(_msg) == true) { - console.log("yes"); + uint64 poolId = ConnectorMessages.parseAddPool(_msg); + console.log(poolId); + connector.addPool(poolId); } else { - console.log("no"); + require(false, "invalid-message"); } - // TODO: actually decode - connector.addPool(1); } } diff --git a/src/token/restricted.sol b/src/token/restricted.sol index 35a90cdb..7bbc42ce 100644 --- a/src/token/restricted.sol +++ b/src/token/restricted.sol @@ -8,8 +8,13 @@ interface MemberlistLike { function member(address) external; } -interface RestrictedTokenLike { +interface ERC20Like { + function mint(address usr, uint wad) external; +} + +interface RestrictedTokenLike is ERC20Like { function memberlist() external view returns (address); + function hasMember(address usr) external view returns (bool); } // Only mebmber with a valid (not expired) membership should be allowed to receive tokens diff --git a/test/Connector.t.sol b/test/Connector.t.sol index b8cd88c7..15b2dd16 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -24,26 +24,27 @@ contract ConnectorTest is Test { homeConnector = new MockHomeConnector(address(bridgedRouter)); } - function testAddingPoolWorks(uint poolId) public { + function testAddingPoolWorks(uint64 poolId) public { + vm.assume(poolId > 0); homeConnector.addPool(poolId); - (uint actualPoolId) = bridgedConnector.pools(poolId); - assertEq(actualPoolId, poolId); + (uint64 actualPoolId) = bridgedConnector.pools(poolId); + assertEq(uint256(actualPoolId), uint256(poolId)); } - function testAddingPoolAsNonRouterFails(uint poolId) public { } - function testAddingTranchesWorks(uint poolId, string memory trancheId) public { } - function testAddingTranchesAsNonRouterFails(uint poolId, string memory trancheId) public { } - function testUpdatingMemberWorks(uint poolId) public { } - function testUpdatingMemberAsNonRouterFails(uint poolId) public { } - function testUpdatingMemberForNonExistentPoolFails(uint poolId) public { } - function testUpdatingMemberForNonExistentTrancheFails(uint poolId) public { } - function testUpdatingTokenPriceWorks(uint poolId) public { } - function testUpdatingTokenPriceAsNonRouterFails(uint poolId) public { } - function testUpdatingTokenPriceForNonExistentPoolFails(uint poolId) public { } - function testUpdatingTokenPriceForNonExistentTrancheFails(uint poolId) public { } - function testTransferToWorks(uint poolId) public { } - function testTransferToAsNonRouterFails(uint poolId) public { } - function testTransferToForNonExistentPoolFails(uint poolId) public { } - function testTransferToForNonExistentTrancheFails(uint poolId) public { } + function testAddingPoolAsNonRouterFails(uint64 poolId) public { } + function testAddingTranchesWorks(uint64 poolId, string memory trancheId) public { } + function testAddingTranchesAsNonRouterFails(uint64 poolId, string memory trancheId) public { } + function testUpdatingMemberWorks(uint64 poolId) public { } + function testUpdatingMemberAsNonRouterFails(uint64 poolId) public { } + function testUpdatingMemberForNonExistentPoolFails(uint64 poolId) public { } + function testUpdatingMemberForNonExistentTrancheFails(uint64 poolId) public { } + function testUpdatingTokenPriceWorks(uint64 poolId) public { } + function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId) public { } + function testUpdatingTokenPriceForNonExistentPoolFails(uint64 poolId) public { } + function testUpdatingTokenPriceForNonExistentTrancheFails(uint64 poolId) public { } + function testTransferToWorks(uint64 poolId) public { } + function testTransferToAsNonRouterFails(uint64 poolId) public { } + function testTransferToForNonExistentPoolFails(uint64 poolId) public { } + function testTransferToForNonExistentTrancheFails(uint64 poolId) public { } } \ No newline at end of file diff --git a/test/mock/MockHomeConnector.sol b/test/mock/MockHomeConnector.sol index 580c553e..f820c0fd 100644 --- a/test/mock/MockHomeConnector.sol +++ b/test/mock/MockHomeConnector.sol @@ -2,11 +2,14 @@ pragma solidity ^0.7.6; pragma abicoder v2; +import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol"; import {IMessageRecipient} from "@nomad-xyz/contracts-core/contracts/interfaces/IMessageRecipient.sol"; import {ConnectorMessages} from "src/Messages.sol"; import "forge-std/Test.sol"; contract MockHomeConnector is Test { + using TypedMemView for bytes; + using TypedMemView for bytes29; using ConnectorMessages for bytes29; IMessageRecipient public immutable home; @@ -23,7 +26,7 @@ contract MockHomeConnector is Test { home = IMessageRecipient(home_); } - function addPool(uint poolId) public returns (bool) { + function addPool(uint64 poolId) public returns (bool) { bytes memory _message = ConnectorMessages.formatAddPool(poolId); home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); return true; From 950a1cf3c80cb746ac5687fb7d33b44fbbe7b88b Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 21 Jun 2022 15:30:52 +0200 Subject: [PATCH 16/30] Start working on adding tranches --- src/Connector.sol | 14 +++++++---- src/Messages.sol | 41 ++++++++++++++++++++++++++++++--- src/routers/nomad/Router.sol | 17 +++++--------- test/Connector.t.sol | 17 +++++++++++--- test/mock/MockHomeConnector.sol | 6 +++++ 5 files changed, 73 insertions(+), 22 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 017e7676..2e67defc 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -25,6 +25,7 @@ contract CentrifugeConnector is Test { struct Pool { uint64 poolId; + uint256 createdAt; mapping(string => Tranche) tranches; } @@ -35,7 +36,7 @@ contract CentrifugeConnector is Test { event Deny(address indexed user); event File(bytes32 indexed what, address data); event PoolAdded(uint256 indexed poolId); - event TrancheAdded(uint256 indexed poolId, string indexed trancheId, address indexed token); + event TrancheAdded(uint256 indexed poolId, uint8[] indexed trancheId, address indexed token); constructor(address router_, address tokenFactory_) { router = RouterLike(router_); @@ -76,20 +77,23 @@ contract CentrifugeConnector is Test { console.log("Adding a pool in Connector"); Pool storage pool = pools[poolId]; pool.poolId = poolId; + pool.createdAt = block.timestamp; emit PoolAdded(poolId); } - function addTranche(uint64 poolId, string calldata trancheId) + function addTranche(uint64 poolId, uint8[] calldata trancheId) public onlyRouter { Pool storage pool = pools[poolId]; - Tranche storage tranche = pool.tranches[trancheId]; + require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); + + // Tranche storage tranche = pool.tranches[trancheId]; // Deploy restricted token // TODO: set actual symbol and name - tranche.token = tokenFactory.newRestrictedToken("SYMBOL", "Name"); - emit TrancheAdded(poolId, trancheId, tranche.token); + address token = tokenFactory.newRestrictedToken("SYMBOL", "Name"); + emit TrancheAdded(poolId, trancheId, token); } function updateTokenPrice( diff --git a/src/Messages.sol b/src/Messages.sol index 97acea60..f15a9b4e 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -16,14 +16,24 @@ library ConnectorMessages { TransferTo } - function formatAddPool(uint64 poolId) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.AddPool), poolId); - } + uint constant TRANCHE_ID_LENGTH = 16; // [u8, 16] function messageType(bytes29 _msg) internal pure returns (Call _call) { _call = Call(uint8(_msg.indexUint(0, 1))); } + /** + * Add pool + * + * 0-1: call type + * 1-5: poolId + * + * TODO: consider adding a message ID + */ + function formatAddPool(uint64 poolId) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.AddPool), poolId); + } + function isAddPool(bytes29 _msg) internal pure returns (bool) { return messageType(_msg) == Call.AddPool; } @@ -31,4 +41,29 @@ library ConnectorMessages { function parseAddPool(bytes29 _msg) internal pure returns (uint64 poolId) { return uint64(_msg.indexUint(1, 8)); } + + /** + * Add tranche + * + * 0-1: call type + * 1-5: poolId + * 6-22: trancheId + * + * TODO: consider adding a message ID + */ + function formatAddTranche(uint64 poolId, uint8[] memory trancheId) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.AddTranche), poolId, trancheId); + } + + function isAddTranche(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.AddTranche; + } + + function parseAddTranche(bytes29 _msg) internal pure returns (uint64 poolId, uint8[] memory trancheId) { + poolId = uint64(_msg.indexUint(1, 8)); + // trancheId = uint8[]; + // for (uint i = 0; i < 16; i++) { + // trancheId.push(uint8(_msg.indexUint(i + 5, 1))); + // } + } } \ No newline at end of file diff --git a/src/routers/nomad/Router.sol b/src/routers/nomad/Router.sol index 305c1010..3f8a9504 100644 --- a/src/routers/nomad/Router.sol +++ b/src/routers/nomad/Router.sol @@ -9,6 +9,7 @@ import "forge-std/Test.sol"; interface ConnectorLike { function addPool(uint64 poolId) external; + function addTranche(uint64 poolId, uint8[] calldata trancheId) external; } contract ConnectorRouter is Router, Test { @@ -24,17 +25,6 @@ contract ConnectorRouter is Router, Test { connector = ConnectorLike(connector_); } - function updateInvestOrder( - uint256 poolId, - uint256[] calldata trancheId, - uint256 amount - ) external { - console.log(poolId); - console.log(amount); - // TODO: send message to Nomad Home contract by calling send() - return; - } - function send(bytes memory message) internal { (_home()).dispatch( CENTRIFUGE_CHAIN_DOMAIN, @@ -55,6 +45,11 @@ contract ConnectorRouter is Router, Test { uint64 poolId = ConnectorMessages.parseAddPool(_msg); console.log(poolId); connector.addPool(poolId); + } else if (ConnectorMessages.isAddTranche(_msg) == true) { + (uint64 poolId, uint8[] memory trancheId) = ConnectorMessages.parseAddTranche(_msg); + console.log(poolId); + // console.log(trancheId[0]); + connector.addTranche(poolId, trancheId); } else { require(false, "invalid-message"); } diff --git a/test/Connector.t.sol b/test/Connector.t.sol index 15b2dd16..d8ed2de6 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -25,14 +25,25 @@ contract ConnectorTest is Test { } function testAddingPoolWorks(uint64 poolId) public { - vm.assume(poolId > 0); homeConnector.addPool(poolId); - (uint64 actualPoolId) = bridgedConnector.pools(poolId); + (uint64 actualPoolId,) = bridgedConnector.pools(poolId); assertEq(uint256(actualPoolId), uint256(poolId)); } function testAddingPoolAsNonRouterFails(uint64 poolId) public { } - function testAddingTranchesWorks(uint64 poolId, string memory trancheId) public { } + + function testAddingSingleTrancheWorks(uint64 poolId) public { + homeConnector.addPool(poolId); + (uint64 actualPoolId,) = bridgedConnector.pools(poolId); + assertEq(uint256(actualPoolId), uint256(poolId)); + + homeConnector.addTranche(poolId, new uint8[](16)); + // TODO: check tranche token existence + assertEq(uint256(3), uint256(4)); + } + + function testAddingMultipleTranchesWorks(uint64 poolId, string memory trancheId) public {} + function testAddingTranchesAsNonRouterFails(uint64 poolId, string memory trancheId) public { } function testUpdatingMemberWorks(uint64 poolId) public { } function testUpdatingMemberAsNonRouterFails(uint64 poolId) public { } diff --git a/test/mock/MockHomeConnector.sol b/test/mock/MockHomeConnector.sol index f820c0fd..59b3e383 100644 --- a/test/mock/MockHomeConnector.sol +++ b/test/mock/MockHomeConnector.sol @@ -31,4 +31,10 @@ contract MockHomeConnector is Test { home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); return true; } + + function addTranche(uint64 poolId, uint8[] calldata trancheId) public returns (bool) { + bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId); + home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); + return true; + } } From d86ec5b1d71a8791337e97a3b91f3b7703347344 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 21 Jun 2022 20:22:15 +0200 Subject: [PATCH 17/30] Fix adding tranches --- src/Connector.sol | 31 +++++++++++++++++-------------- src/Messages.sol | 19 ++++++++----------- src/routers/nomad/Router.sol | 4 ++-- test/Connector.t.sol | 9 +++++---- test/mock/MockHomeConnector.sol | 2 +- 5 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 2e67defc..644bb172 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -18,25 +18,26 @@ contract CentrifugeConnector is Test { // --- Storage --- mapping(address => uint256) public wards; - struct Tranche { - uint256 latestPrice; // [ray] - address token; - } - struct Pool { uint64 poolId; uint256 createdAt; - mapping(string => Tranche) tranches; } mapping(uint64 => Pool) public pools; + struct Tranche { + uint256 latestPrice; // [ray] + address token; + } + + mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; + // --- Events --- event Rely(address indexed user); event Deny(address indexed user); event File(bytes32 indexed what, address data); event PoolAdded(uint256 indexed poolId); - event TrancheAdded(uint256 indexed poolId, uint8[] indexed trancheId, address indexed token); + event TrancheAdded(uint256 indexed poolId, bytes16 indexed trancheId, address indexed token); constructor(address router_, address tokenFactory_) { router = RouterLike(router_); @@ -81,45 +82,47 @@ contract CentrifugeConnector is Test { emit PoolAdded(poolId); } - function addTranche(uint64 poolId, uint8[] calldata trancheId) + function addTranche(uint64 poolId, bytes16 trancheId) public onlyRouter { Pool storage pool = pools[poolId]; require(pool.createdAt > 0, "CentrifugeConnector/invalid-pool"); - // Tranche storage tranche = pool.tranches[trancheId]; + Tranche storage tranche = tranches[poolId][trancheId]; + tranche.latestPrice = 1*10**27; // Deploy restricted token // TODO: set actual symbol and name address token = tokenFactory.newRestrictedToken("SYMBOL", "Name"); + tranche.token = token; emit TrancheAdded(poolId, trancheId, token); } function updateTokenPrice( uint64 poolId, - string calldata trancheId, + bytes16 trancheId, uint256 price ) public onlyRouter {} function updateMember( uint64 poolId, - string calldata trancheId, + bytes16 trancheId, address user, uint256 validUntil ) public onlyRouter { - RestrictedTokenLike token = RestrictedTokenLike(pools[poolId].tranches[trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); MemberlistLike memberlist = MemberlistLike(token.memberlist()); memberlist.updateMember(user, validUntil); } function transferTo( uint64 poolId, - string calldata trancheId, + bytes16 trancheId, address user, uint256 amount ) public onlyRouter { - RestrictedTokenLike token = RestrictedTokenLike(pools[poolId].tranches[trancheId].token); + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); require(token.hasMember(user), "CentrifugeConnector/not-a-member"); token.mint(user, amount); } diff --git a/src/Messages.sol b/src/Messages.sol index f15a9b4e..5c6a0253 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -25,8 +25,8 @@ library ConnectorMessages { /** * Add pool * - * 0-1: call type - * 1-5: poolId + * 1: call type (uint8 = 1 byte) + * 2-9: poolId (uint64 = 8 bytes) * * TODO: consider adding a message ID */ @@ -45,13 +45,13 @@ library ConnectorMessages { /** * Add tranche * - * 0-1: call type - * 1-5: poolId - * 6-22: trancheId + * 1: call type (uint8 = 1 byte) + * 2-9: poolId (uint64 = 8 bytes) + * 10-26: trancheId (16 bytes) * * TODO: consider adding a message ID */ - function formatAddTranche(uint64 poolId, uint8[] memory trancheId) internal pure returns (bytes memory) { + function formatAddTranche(uint64 poolId, bytes16 trancheId) internal pure returns (bytes memory) { return abi.encodePacked(uint8(Call.AddTranche), poolId, trancheId); } @@ -59,11 +59,8 @@ library ConnectorMessages { return messageType(_msg) == Call.AddTranche; } - function parseAddTranche(bytes29 _msg) internal pure returns (uint64 poolId, uint8[] memory trancheId) { + function parseAddTranche(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId) { poolId = uint64(_msg.indexUint(1, 8)); - // trancheId = uint8[]; - // for (uint i = 0; i < 16; i++) { - // trancheId.push(uint8(_msg.indexUint(i + 5, 1))); - // } + trancheId = bytes16(_msg.index(9, 16)); } } \ No newline at end of file diff --git a/src/routers/nomad/Router.sol b/src/routers/nomad/Router.sol index 3f8a9504..9ebf4c7f 100644 --- a/src/routers/nomad/Router.sol +++ b/src/routers/nomad/Router.sol @@ -9,7 +9,7 @@ import "forge-std/Test.sol"; interface ConnectorLike { function addPool(uint64 poolId) external; - function addTranche(uint64 poolId, uint8[] calldata trancheId) external; + function addTranche(uint64 poolId, bytes16 trancheId) external; } contract ConnectorRouter is Router, Test { @@ -46,7 +46,7 @@ contract ConnectorRouter is Router, Test { console.log(poolId); connector.addPool(poolId); } else if (ConnectorMessages.isAddTranche(_msg) == true) { - (uint64 poolId, uint8[] memory trancheId) = ConnectorMessages.parseAddTranche(_msg); + (uint64 poolId, bytes16 trancheId) = ConnectorMessages.parseAddTranche(_msg); console.log(poolId); // console.log(trancheId[0]); connector.addTranche(poolId, trancheId); diff --git a/test/Connector.t.sol b/test/Connector.t.sol index d8ed2de6..55c7023c 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -32,14 +32,15 @@ contract ConnectorTest is Test { function testAddingPoolAsNonRouterFails(uint64 poolId) public { } - function testAddingSingleTrancheWorks(uint64 poolId) public { + function testAddingSingleTrancheWorks(uint64 poolId, bytes16 trancheId) public { homeConnector.addPool(poolId); (uint64 actualPoolId,) = bridgedConnector.pools(poolId); assertEq(uint256(actualPoolId), uint256(poolId)); - homeConnector.addTranche(poolId, new uint8[](16)); - // TODO: check tranche token existence - assertEq(uint256(3), uint256(4)); + homeConnector.addTranche(poolId, trancheId); + (uint256 latestPrice, address token) = bridgedConnector.tranches(poolId, trancheId); + assertTrue(latestPrice > 0); + assertTrue(token != address(0)); } function testAddingMultipleTranchesWorks(uint64 poolId, string memory trancheId) public {} diff --git a/test/mock/MockHomeConnector.sol b/test/mock/MockHomeConnector.sol index 59b3e383..4b17ccf2 100644 --- a/test/mock/MockHomeConnector.sol +++ b/test/mock/MockHomeConnector.sol @@ -32,7 +32,7 @@ contract MockHomeConnector is Test { return true; } - function addTranche(uint64 poolId, uint8[] calldata trancheId) public returns (bool) { + function addTranche(uint64 poolId, bytes16 trancheId) public returns (bool) { bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId); home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); return true; From e5dfd5c45b9dfd4da3e8ce38b372f861a3398588 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 21 Jun 2022 21:13:40 +0200 Subject: [PATCH 18/30] Support updating members, add more tests --- script/Connector.s.sol | 7 ++-- src/Connector.sol | 18 ++++++---- src/Messages.sol | 41 +++++++++++++++------ src/routers/nomad/Router.sol | 10 ++++-- src/token/factory.sol | 18 +++++++++- src/token/memberlist.sol | 24 ++++++------- src/token/restricted.sol | 1 + test/Connector.t.sol | 63 +++++++++++++++++++++++++++++---- test/Messages.t.sol | 38 ++++++++++++++++++++ test/mock/MockHomeConnector.sol | 12 ++++--- 10 files changed, 186 insertions(+), 46 deletions(-) create mode 100644 test/Messages.t.sol diff --git a/script/Connector.s.sol b/script/Connector.s.sol index 19f0c0df..7c09cbcd 100644 --- a/script/Connector.s.sol +++ b/script/Connector.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.7.6; import { ConnectorRouter } from "src/routers/nomad/Router.sol"; import { CentrifugeConnector } from "src/Connector.sol"; -import { RestrictedTokenFactory } from "src/token/factory.sol"; +import { RestrictedTokenFactory, MemberlistFactory } from "src/token/factory.sol"; import "forge-std/Script.sol"; contract ConnectorScript is Script { @@ -13,8 +13,11 @@ contract ConnectorScript is Script { vm.broadcast(); address tokenFactory_ = address(new RestrictedTokenFactory()); + address memberlistFactory_ = address(new MemberlistFactory()); ConnectorRouter router = new ConnectorRouter(address(0)); - new CentrifugeConnector(address(router), tokenFactory_); + new CentrifugeConnector(address(router), tokenFactory_, memberlistFactory_); + + // TODO: file connector on router } } diff --git a/src/Connector.sol b/src/Connector.sol index 644bb172..b98047d4 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -2,7 +2,7 @@ pragma solidity ^0.7.6; pragma abicoder v2; -import { RestrictedTokenFactoryLike } from "./token/factory.sol"; +import { RestrictedTokenFactoryLike, MemberlistFactoryLike } from "./token/factory.sol"; import { RestrictedTokenLike } from "./token/restricted.sol"; import { MemberlistLike } from "./token/memberlist.sol"; import "forge-std/Test.sol"; @@ -14,10 +14,9 @@ contract CentrifugeConnector is Test { RouterLike public router; RestrictedTokenFactoryLike public immutable tokenFactory; + MemberlistFactoryLike public immutable memberlistFactory; // --- Storage --- - mapping(address => uint256) public wards; - struct Pool { uint64 poolId; uint256 createdAt; @@ -26,12 +25,15 @@ contract CentrifugeConnector is Test { mapping(uint64 => Pool) public pools; struct Tranche { - uint256 latestPrice; // [ray] address token; + uint256 latestPrice; // [ray] + uint256 lastPriceUpdate; } mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; + mapping(address => uint256) public wards; + // --- Events --- event Rely(address indexed user); event Deny(address indexed user); @@ -39,9 +41,10 @@ contract CentrifugeConnector is Test { event PoolAdded(uint256 indexed poolId); event TrancheAdded(uint256 indexed poolId, bytes16 indexed trancheId, address indexed token); - constructor(address router_, address tokenFactory_) { + constructor(address router_, address tokenFactory_, address memberlistFactory_) { router = RouterLike(router_); tokenFactory = RestrictedTokenFactoryLike(tokenFactory_); + memberlistFactory = MemberlistFactoryLike(memberlistFactory_); wards[msg.sender] = 1; emit Rely(msg.sender); } @@ -75,7 +78,6 @@ contract CentrifugeConnector is Test { // --- Internal --- function addPool(uint64 poolId) public onlyRouter { - console.log("Adding a pool in Connector"); Pool storage pool = pools[poolId]; pool.poolId = poolId; pool.createdAt = block.timestamp; @@ -96,6 +98,10 @@ contract CentrifugeConnector is Test { // TODO: set actual symbol and name address token = tokenFactory.newRestrictedToken("SYMBOL", "Name"); tranche.token = token; + + address memberlist = memberlistFactory.newMemberlist(); + RestrictedTokenLike(token).depend("memberlist", memberlist); + emit TrancheAdded(poolId, trancheId, token); } diff --git a/src/Messages.sol b/src/Messages.sol index 5c6a0253..e5ba1867 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -16,8 +16,6 @@ library ConnectorMessages { TransferTo } - uint constant TRANCHE_ID_LENGTH = 16; // [u8, 16] - function messageType(bytes29 _msg) internal pure returns (Call _call) { _call = Call(uint8(_msg.indexUint(0, 1))); } @@ -25,10 +23,8 @@ library ConnectorMessages { /** * Add pool * - * 1: call type (uint8 = 1 byte) - * 2-9: poolId (uint64 = 8 bytes) - * - * TODO: consider adding a message ID + * 0: call type (uint8 = 1 byte) + * 1-8: poolId (uint64 = 8 bytes) */ function formatAddPool(uint64 poolId) internal pure returns (bytes memory) { return abi.encodePacked(uint8(Call.AddPool), poolId); @@ -45,11 +41,9 @@ library ConnectorMessages { /** * Add tranche * - * 1: call type (uint8 = 1 byte) - * 2-9: poolId (uint64 = 8 bytes) - * 10-26: trancheId (16 bytes) - * - * TODO: consider adding a message ID + * 0: call type (uint8 = 1 byte) + * 1-8: poolId (uint64 = 8 bytes) + * 9-25: trancheId (16 bytes) */ function formatAddTranche(uint64 poolId, bytes16 trancheId) internal pure returns (bytes memory) { return abi.encodePacked(uint8(Call.AddTranche), poolId, trancheId); @@ -63,4 +57,29 @@ library ConnectorMessages { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); } + + /** + * Update member + * + * 0: call type (uint8 = 1 byte) + * 1-8: poolId (uint64 = 8 bytes) + * 9-25: trancheId (16 bytes) + * 26-46: user (Ethereum address, 20 bytes) + * 47-78: amount (uint256 = 32 bytes) + */ + function formatUpdateMember(uint64 poolId, bytes16 trancheId, address user, uint256 amount) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.UpdateMember), poolId, trancheId, user, amount); + } + + function isUpdateMember(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.UpdateMember; + } + + function parseUpdateMember(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, address user, uint256 amount) { + poolId = uint64(_msg.indexUint(1, 8)); + trancheId = bytes16(_msg.index(9, 16)); + user = address(bytes20(_msg.index(25, 20))); + amount = uint256(_msg.index(45, 32)); + } + } \ No newline at end of file diff --git a/src/routers/nomad/Router.sol b/src/routers/nomad/Router.sol index 9ebf4c7f..b1a4c34d 100644 --- a/src/routers/nomad/Router.sol +++ b/src/routers/nomad/Router.sol @@ -10,6 +10,7 @@ import "forge-std/Test.sol"; interface ConnectorLike { function addPool(uint64 poolId) external; function addTranche(uint64 poolId, bytes16 trancheId) external; + function updateMember(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) external; } contract ConnectorRouter is Router, Test { @@ -43,13 +44,16 @@ contract ConnectorRouter is Router, Test { bytes29 _msg = _message.ref(0); if (ConnectorMessages.isAddPool(_msg) == true) { uint64 poolId = ConnectorMessages.parseAddPool(_msg); - console.log(poolId); connector.addPool(poolId); } else if (ConnectorMessages.isAddTranche(_msg) == true) { (uint64 poolId, bytes16 trancheId) = ConnectorMessages.parseAddTranche(_msg); - console.log(poolId); - // console.log(trancheId[0]); connector.addTranche(poolId, trancheId); + } else if (ConnectorMessages.isUpdateMember(_msg) == true) { + (uint64 poolId, bytes16 trancheId, address user, uint256 amount) = ConnectorMessages.parseUpdateMember(_msg); + console.log(poolId); + console.log(user); + console.log(amount); + connector.updateMember(poolId, trancheId, user, amount); } else { require(false, "invalid-message"); } diff --git a/src/token/factory.sol b/src/token/factory.sol index 2b26798a..fabe94b9 100644 --- a/src/token/factory.sol +++ b/src/token/factory.sol @@ -2,6 +2,7 @@ pragma solidity >=0.7.6; import { RestrictedToken } from "./restricted.sol"; +import { Memberlist } from "./memberlist.sol"; interface RestrictedTokenFactoryLike { function newRestrictedToken(string calldata, string calldata) external returns (address); @@ -14,4 +15,19 @@ contract RestrictedTokenFactory { token.deny(address(this)); return address(token); } -} \ No newline at end of file +} + +interface MemberlistFactoryLike { + function newMemberlist() external returns (address); +} + +contract MemberlistFactory { + function newMemberlist() public returns (address memberList) { + Memberlist memberlist = new Memberlist(); + + memberlist.rely(msg.sender); + memberlist.deny(address(this)); + + return (address(memberlist)); + } +} diff --git a/src/token/memberlist.sol b/src/token/memberlist.sol index 126f0b84..9896aa45 100644 --- a/src/token/memberlist.sol +++ b/src/token/memberlist.sol @@ -3,26 +3,30 @@ pragma solidity >=0.7.6; interface MemberlistLike { function updateMember(address usr, uint validUntil) external; + function members(address usr) external view returns (uint); } contract Memberlist { - mapping(address => uint256) public wards; - uint constant minimumDelay = 7 days; - modifier auth { - require(wards[msg.sender] == 1, "not-authorized"); - _; - } + mapping (address => uint) public members; + + // --- Auth --- + mapping (address => uint) public wards; + function rely(address usr) public auth { wards[usr] = 1; } + function deny(address usr) public auth { wards[usr] = 0; } + modifier auth { require(wards[msg.sender] == 1); _; } // --- Math --- function safeAdd(uint x, uint y) internal pure returns (uint z) { require((z = x + y) >= x, "math-add-overflow"); } - // -- Members-- - mapping (address => uint) public members; + constructor() { + wards[msg.sender] = 1; + } + function updateMember(address usr, uint validUntil) public auth { require((safeAdd(block.timestamp, minimumDelay)) < validUntil); members[usr] = validUntil; @@ -34,10 +38,6 @@ contract Memberlist { } } - constructor() { - wards[msg.sender] = 1; - } - function member(address usr) public view { require((members[usr] >= block.timestamp), "not-allowed-to-hold-token"); } diff --git a/src/token/restricted.sol b/src/token/restricted.sol index 7bbc42ce..a7d85c17 100644 --- a/src/token/restricted.sol +++ b/src/token/restricted.sol @@ -15,6 +15,7 @@ interface ERC20Like { interface RestrictedTokenLike is ERC20Like { function memberlist() external view returns (address); function hasMember(address usr) external view returns (bool); + function depend(bytes32 contractName, address addr) external; } // Only mebmber with a valid (not expired) membership should be allowed to receive tokens diff --git a/test/Connector.t.sol b/test/Connector.t.sol index 55c7023c..9d0c68a6 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -3,7 +3,9 @@ pragma solidity ^0.7.6; pragma abicoder v2; import { CentrifugeConnector } from "src/Connector.sol"; -import { RestrictedTokenFactory } from "src/token/factory.sol"; +import { RestrictedTokenFactory, MemberlistFactory } from "src/token/factory.sol"; +import { RestrictedTokenLike } from "src/token/restricted.sol"; +import { MemberlistLike } from "src/token/memberlist.sol"; import { MockHomeConnector } from "./mock/MockHomeConnector.sol"; import { ConnectorRouter } from "src/routers/nomad/Router.sol"; import "forge-std/Test.sol"; @@ -16,8 +18,9 @@ contract ConnectorTest is Test { function setUp() public { address tokenFactory_ = address(new RestrictedTokenFactory()); + address memberlistFactory_ = address(new MemberlistFactory()); - bridgedConnector = new CentrifugeConnector(address(this), tokenFactory_); + bridgedConnector = new CentrifugeConnector(address(this), tokenFactory_, memberlistFactory_); bridgedRouter = new ConnectorRouter(address(bridgedConnector)); bridgedConnector.file("router", address(bridgedRouter)); @@ -30,7 +33,10 @@ contract ConnectorTest is Test { assertEq(uint256(actualPoolId), uint256(poolId)); } - function testAddingPoolAsNonRouterFails(uint64 poolId) public { } + function testAddingPoolAsNonRouterFails(uint64 poolId) public { + vm.expectRevert(bytes("CentrifugeConnector/not-the-router")); + bridgedConnector.addPool(poolId); + } function testAddingSingleTrancheWorks(uint64 poolId, bytes16 trancheId) public { homeConnector.addPool(poolId); @@ -38,15 +44,51 @@ contract ConnectorTest is Test { assertEq(uint256(actualPoolId), uint256(poolId)); homeConnector.addTranche(poolId, trancheId); - (uint256 latestPrice, address token) = bridgedConnector.tranches(poolId, trancheId); + (address token, uint256 latestPrice,) = bridgedConnector.tranches(poolId, trancheId); assertTrue(latestPrice > 0); assertTrue(token != address(0)); } - function testAddingMultipleTranchesWorks(uint64 poolId, string memory trancheId) public {} + function testAddingMultipleTranchesWorks(uint64 poolId, bytes16[] calldata trancheIds) public { + vm.assume(trancheIds.length > 0 && trancheIds.length <= 5); + + homeConnector.addPool(poolId); + + for (uint i = 0; i < trancheIds.length; i++) { + homeConnector.addTranche(poolId, trancheIds[i]); + (address token, uint256 latestPrice,) = bridgedConnector.tranches(poolId, trancheIds[i]); + assertTrue(latestPrice > 0); + assertTrue(token != address(0)); + } + } - function testAddingTranchesAsNonRouterFails(uint64 poolId, string memory trancheId) public { } - function testUpdatingMemberWorks(uint64 poolId) public { } + function testAddingTranchesAsNonRouterFails(uint64 poolId, bytes16 trancheId) public { + homeConnector.addPool(poolId); + vm.expectRevert(bytes("CentrifugeConnector/not-the-router")); + bridgedConnector.addTranche(poolId, trancheId); + } + + function testAddingTranchesForNonExistentPoolFails(uint64 poolId, bytes16 trancheId) public { + vm.expectRevert(bytes("CentrifugeConnector/invalid-pool")); + homeConnector.addTranche(poolId, trancheId); + } + + function testUpdatingMemberWorks(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) public { + vm.assume(validUntil > block.timestamp); + vm.assume(user != address(0)); + + homeConnector.addPool(poolId); + homeConnector.addTranche(poolId, trancheId); + homeConnector.updateMember(poolId, trancheId, user, validUntil); + + (address token_,,) = bridgedConnector.tranches(poolId, trancheId); + RestrictedTokenLike token = RestrictedTokenLike(token_); + assertTrue(token.hasMember(user)); + + MemberlistLike memberlist = MemberlistLike(token.memberlist()); + assertEq(memberlist.members(user), validUntil); + } + function testUpdatingMemberAsNonRouterFails(uint64 poolId) public { } function testUpdatingMemberForNonExistentPoolFails(uint64 poolId) public { } function testUpdatingMemberForNonExistentTrancheFails(uint64 poolId) public { } @@ -59,4 +101,11 @@ contract ConnectorTest is Test { function testTransferToForNonExistentPoolFails(uint64 poolId) public { } function testTransferToForNonExistentTrancheFails(uint64 poolId) public { } + // function setupPool() internal returns (uint64 poolId, bytes16[] trancheIds) { + // uint64 poolId = 1; + // bytes16[] trancheIds = new bytes16[](); + // homeConnector.addPool(poolId); + // homeConnector.addTranche(poolId, trancheId); + // } + } \ No newline at end of file diff --git a/test/Messages.t.sol b/test/Messages.t.sol new file mode 100644 index 00000000..79343aa4 --- /dev/null +++ b/test/Messages.t.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.7.6; +pragma abicoder v2; + +import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol"; +import { ConnectorMessages } from "src/Messages.sol"; +import "forge-std/Test.sol"; + +contract MessagesTest is Test { + using TypedMemView for bytes; + using TypedMemView for bytes29; + using ConnectorMessages for bytes29; + + function setUp() public {} + + function testAddPoolEquivalence(uint64 poolId) public { + bytes memory _message = ConnectorMessages.formatAddPool(poolId); + uint64 decodedPoolId = ConnectorMessages.parseAddPool(_message.ref(0)); + assertEq(uint256(decodedPoolId), uint256(poolId)); + } + + function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId) public { + bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId); + (uint64 decodedPoolId, bytes16 decodedTrancheId) = ConnectorMessages.parseAddTranche(_message.ref(0)); + assertEq(uint256(decodedPoolId), uint256(poolId)); + assertEq(decodedTrancheId, trancheId); + } + + function testUpdateMemberEquivalence(uint64 poolId, bytes16 trancheId, address user, uint256 amount) public { + bytes memory _message = ConnectorMessages.formatUpdateMember(poolId, trancheId, user, amount); + (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedUser, uint256 decodedAmount) = ConnectorMessages.parseUpdateMember(_message.ref(0)); + assertEq(uint256(decodedPoolId), uint256(poolId)); + assertEq(decodedTrancheId, trancheId); + assertEq(decodedUser, user); + assertEq(decodedAmount, amount); + } + +} \ No newline at end of file diff --git a/test/mock/MockHomeConnector.sol b/test/mock/MockHomeConnector.sol index 4b17ccf2..09c580d0 100644 --- a/test/mock/MockHomeConnector.sol +++ b/test/mock/MockHomeConnector.sol @@ -26,15 +26,19 @@ contract MockHomeConnector is Test { home = IMessageRecipient(home_); } - function addPool(uint64 poolId) public returns (bool) { + function addPool(uint64 poolId) public { bytes memory _message = ConnectorMessages.formatAddPool(poolId); home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); - return true; } - function addTranche(uint64 poolId, bytes16 trancheId) public returns (bool) { + function addTranche(uint64 poolId, bytes16 trancheId) public { bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId); home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); - return true; } + + function updateMember(uint64 poolId, bytes16 trancheId, address user, uint256 amount) public { + bytes memory _message = ConnectorMessages.formatUpdateMember(poolId, trancheId, user, amount); + home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); + } + } From 8b7a487c1785525ee0267a792daa4d03116fa42c Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 21 Jun 2022 21:16:07 +0200 Subject: [PATCH 19/30] Remove comments --- test/Connector.t.sol | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test/Connector.t.sol b/test/Connector.t.sol index 9d0c68a6..908abf69 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -101,11 +101,4 @@ contract ConnectorTest is Test { function testTransferToForNonExistentPoolFails(uint64 poolId) public { } function testTransferToForNonExistentTrancheFails(uint64 poolId) public { } - // function setupPool() internal returns (uint64 poolId, bytes16[] trancheIds) { - // uint64 poolId = 1; - // bytes16[] trancheIds = new bytes16[](); - // homeConnector.addPool(poolId); - // homeConnector.addTranche(poolId, trancheId); - // } - } \ No newline at end of file From 6a470d83f32e0a73d646927ebd7d739f6723401d Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 21 Jun 2022 22:47:19 +0200 Subject: [PATCH 20/30] Add update token price --- src/Connector.sol | 6 +++++- src/Messages.sol | 22 ++++++++++++++++++++++ src/routers/nomad/Router.sol | 7 ++++--- test/Connector.t.sol | 21 +++++++++++++++++++-- test/Messages.t.sol | 8 ++++++++ test/mock/MockHomeConnector.sol | 5 +++++ 6 files changed, 63 insertions(+), 6 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index b98047d4..867543b9 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -109,7 +109,11 @@ contract CentrifugeConnector is Test { uint64 poolId, bytes16 trancheId, uint256 price - ) public onlyRouter {} + ) public onlyRouter { + Tranche storage tranche = tranches[poolId][trancheId]; + tranche.latestPrice = price; + tranche.lastPriceUpdate = block.timestamp; + } function updateMember( uint64 poolId, diff --git a/src/Messages.sol b/src/Messages.sol index e5ba1867..ced1866f 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -82,4 +82,26 @@ library ConnectorMessages { amount = uint256(_msg.index(45, 32)); } + /** + * Update token price + * + * 0: call type (uint8 = 1 byte) + * 1-8: poolId (uint64 = 8 bytes) + * 9-25: trancheId (16 bytes) + * 26-58: amount (uint256 = 32 bytes) + */ + function formatUpdateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.UpdateTokenPrice), poolId, trancheId, price); + } + + function isUpdateTokenPrice(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.UpdateTokenPrice; + } + + function parseUpdateTokenPrice(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, uint256 price) { + poolId = uint64(_msg.indexUint(1, 8)); + trancheId = bytes16(_msg.index(9, 16)); + price = uint256(_msg.index(25, 32)); + } + } \ No newline at end of file diff --git a/src/routers/nomad/Router.sol b/src/routers/nomad/Router.sol index b1a4c34d..fbdcfd4b 100644 --- a/src/routers/nomad/Router.sol +++ b/src/routers/nomad/Router.sol @@ -11,6 +11,7 @@ interface ConnectorLike { function addPool(uint64 poolId) external; function addTranche(uint64 poolId, bytes16 trancheId) external; function updateMember(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) external; + function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) external; } contract ConnectorRouter is Router, Test { @@ -50,10 +51,10 @@ contract ConnectorRouter is Router, Test { connector.addTranche(poolId, trancheId); } else if (ConnectorMessages.isUpdateMember(_msg) == true) { (uint64 poolId, bytes16 trancheId, address user, uint256 amount) = ConnectorMessages.parseUpdateMember(_msg); - console.log(poolId); - console.log(user); - console.log(amount); connector.updateMember(poolId, trancheId, user, amount); + } else if (ConnectorMessages.isUpdateTokenPrice(_msg) == true) { + (uint64 poolId, bytes16 trancheId, uint256 price) = ConnectorMessages.parseUpdateTokenPrice(_msg); + connector.updateTokenPrice(poolId, trancheId, price); } else { require(false, "invalid-message"); } diff --git a/test/Connector.t.sol b/test/Connector.t.sol index 908abf69..555b27da 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -92,10 +92,27 @@ contract ConnectorTest is Test { function testUpdatingMemberAsNonRouterFails(uint64 poolId) public { } function testUpdatingMemberForNonExistentPoolFails(uint64 poolId) public { } function testUpdatingMemberForNonExistentTrancheFails(uint64 poolId) public { } - function testUpdatingTokenPriceWorks(uint64 poolId) public { } - function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId) public { } + + function testUpdatingTokenPriceWorks(uint64 poolId, bytes16 trancheId, uint256 price) public { + homeConnector.addPool(poolId); + homeConnector.addTranche(poolId, trancheId); + homeConnector.updateTokenPrice(poolId, trancheId, price); + + (, uint256 latestPrice, uint256 lastPriceUpdate) = bridgedConnector.tranches(poolId, trancheId); + assertEq(latestPrice, price); + assertEq(lastPriceUpdate, block.timestamp); + } + + function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId, bytes16 trancheId, uint256 price) public { + homeConnector.addPool(poolId); + homeConnector.addTranche(poolId, trancheId); + vm.expectRevert(bytes("CentrifugeConnector/not-the-router")); + homeConnector.updateTokenPrice(poolId, trancheId, price); + + } function testUpdatingTokenPriceForNonExistentPoolFails(uint64 poolId) public { } function testUpdatingTokenPriceForNonExistentTrancheFails(uint64 poolId) public { } + function testTransferToWorks(uint64 poolId) public { } function testTransferToAsNonRouterFails(uint64 poolId) public { } function testTransferToForNonExistentPoolFails(uint64 poolId) public { } diff --git a/test/Messages.t.sol b/test/Messages.t.sol index 79343aa4..97713863 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -35,4 +35,12 @@ contract MessagesTest is Test { assertEq(decodedAmount, amount); } + function testUpdateTokenPriceEquivalence(uint64 poolId, bytes16 trancheId, uint256 price) public { + bytes memory _message = ConnectorMessages.formatUpdateTokenPrice(poolId, trancheId, price); + (uint64 decodedPoolId, bytes16 decodedTrancheId, uint256 decodedPrice) = ConnectorMessages.parseUpdateTokenPrice(_message.ref(0)); + assertEq(uint256(decodedPoolId), uint256(poolId)); + assertEq(decodedTrancheId, trancheId); + assertEq(decodedPrice, price); + } + } \ No newline at end of file diff --git a/test/mock/MockHomeConnector.sol b/test/mock/MockHomeConnector.sol index 09c580d0..e58fcec7 100644 --- a/test/mock/MockHomeConnector.sol +++ b/test/mock/MockHomeConnector.sol @@ -41,4 +41,9 @@ contract MockHomeConnector is Test { home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); } + function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) public { + bytes memory _message = ConnectorMessages.formatUpdateTokenPrice(poolId, trancheId, price); + home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); + } + } From 9fc98409d384e75171fae4b13a638085910de312 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Sun, 26 Jun 2022 08:35:28 +0200 Subject: [PATCH 21/30] Work on new message encoding and decoding tests --- test/Messages.t.sol | 133 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 122 insertions(+), 11 deletions(-) diff --git a/test/Messages.t.sol b/test/Messages.t.sol index 97713863..a3a4c8ac 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.7.6; pragma abicoder v2; import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol"; -import { ConnectorMessages } from "src/Messages.sol"; +import {ConnectorMessages} from "src/Messages.sol"; import "forge-std/Test.sol"; contract MessagesTest is Test { @@ -13,34 +13,145 @@ contract MessagesTest is Test { function setUp() public {} + function testAddPoolEncoding() public { + assertEq( + ConnectorMessages.formatAddPool(0), + fromHex("010000000000000000") + ); + assertEq( + ConnectorMessages.formatAddPool(1), + fromHex("010000000000000001") + ); + assertEq( + ConnectorMessages.formatAddPool(12378532), + fromHex("010000000000bce1a4") + ); + } + + function testAddPoolDecoding() public { + uint64 actualPoolId1 = ConnectorMessages.parseAddPool(fromHex("010000000000000000").ref(0)); + assertEq( + uint256(actualPoolId1), + 0 + ); + + uint64 actualPoolId2 = ConnectorMessages.parseAddPool(fromHex("010000000000000001").ref(0)); + assertEq( + uint256(actualPoolId1), + 1 + ); + + uint64 actualPoolId3 = ConnectorMessages.parseAddPool(fromHex("010000000000bce1a4").ref(0)); + assertEq( + uint256(actualPoolId1), + 12378532 + ); + } + function testAddPoolEquivalence(uint64 poolId) public { bytes memory _message = ConnectorMessages.formatAddPool(poolId); uint64 decodedPoolId = ConnectorMessages.parseAddPool(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); } - function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId) public { - bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId); - (uint64 decodedPoolId, bytes16 decodedTrancheId) = ConnectorMessages.parseAddTranche(_message.ref(0)); + function testAddTrancheEncoding() public { + assertEq( + ConnectorMessages.formatAddTranche(0, toBytes16(fromHex("100"))), + fromHex("010000000000000000") + ); + } + + function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId) + public + { + bytes memory _message = ConnectorMessages.formatAddTranche( + poolId, + trancheId + ); + (uint64 decodedPoolId, bytes16 decodedTrancheId) = ConnectorMessages + .parseAddTranche(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); } - function testUpdateMemberEquivalence(uint64 poolId, bytes16 trancheId, address user, uint256 amount) public { - bytes memory _message = ConnectorMessages.formatUpdateMember(poolId, trancheId, user, amount); - (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedUser, uint256 decodedAmount) = ConnectorMessages.parseUpdateMember(_message.ref(0)); + function testUpdateMemberEquivalence( + uint64 poolId, + bytes16 trancheId, + address user, + uint256 amount + ) public { + bytes memory _message = ConnectorMessages.formatUpdateMember( + poolId, + trancheId, + user, + amount + ); + ( + uint64 decodedPoolId, + bytes16 decodedTrancheId, + address decodedUser, + uint256 decodedAmount + ) = ConnectorMessages.parseUpdateMember(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); assertEq(decodedUser, user); assertEq(decodedAmount, amount); } - function testUpdateTokenPriceEquivalence(uint64 poolId, bytes16 trancheId, uint256 price) public { - bytes memory _message = ConnectorMessages.formatUpdateTokenPrice(poolId, trancheId, price); - (uint64 decodedPoolId, bytes16 decodedTrancheId, uint256 decodedPrice) = ConnectorMessages.parseUpdateTokenPrice(_message.ref(0)); + function testUpdateTokenPriceEquivalence( + uint64 poolId, + bytes16 trancheId, + uint256 price + ) public { + bytes memory _message = ConnectorMessages.formatUpdateTokenPrice( + poolId, + trancheId, + price + ); + ( + uint64 decodedPoolId, + bytes16 decodedTrancheId, + uint256 decodedPrice + ) = ConnectorMessages.parseUpdateTokenPrice(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); assertEq(decodedPrice, price); } -} \ No newline at end of file + // Convert an hexadecimal character to their value + function fromHexChar(uint8 c) internal pure returns (uint8) { + if (bytes1(c) >= bytes1("0") && bytes1(c) <= bytes1("9")) { + return c - uint8(bytes1("0")); + } + if (bytes1(c) >= bytes1("a") && bytes1(c) <= bytes1("f")) { + return 10 + c - uint8(bytes1("a")); + } + if (bytes1(c) >= bytes1("A") && bytes1(c) <= bytes1("F")) { + return 10 + c - uint8(bytes1("A")); + } + revert("Failed to encode hex char"); + } + + // Convert an hexadecimal string to raw bytes + function fromHex(string memory s) internal pure returns (bytes memory) { + bytes memory ss = bytes(s); + require(ss.length % 2 == 0); // length must be even + bytes memory r = new bytes(ss.length / 2); + + for (uint256 i = 0; i < ss.length / 2; ++i) { + r[i] = bytes1( + fromHexChar(uint8(ss[2 * i])) * + 16 + + fromHexChar(uint8(ss[2 * i + 1])) + ); + } + return r; + } + + function toBytes16(bytes memory f) internal pure returns (bytes16 fc) { + assembly { + fc := mload(add(f, 32)) + } + return fc; + } +} From 32f3da0324d42881d79759c663c1a11390f20813 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Wed, 29 Jun 2022 18:50:56 +0200 Subject: [PATCH 22/30] Set up XCM router and token name + symbol for tranches --- ...{Connector.s.sol => Connector-Nomad.s.sol} | 11 ++-- script/Connector-XCM.s.sol | 23 ++++++++ src/Connector.sol | 13 ++-- src/Messages.sol | 47 ++++++++++++--- src/routers/nomad/Forwarder.sol | 0 src/routers/nomad/Router.sol | 8 +-- src/routers/xcm/Router.sol | 59 +++++++++++++++++++ src/token/erc20.sol | 4 +- src/token/factory.sol | 4 +- src/token/restricted.sol | 4 +- test/Connector.t.sol | 18 +++--- test/mock/MockHomeConnector.sol | 4 +- 12 files changed, 153 insertions(+), 42 deletions(-) rename script/{Connector.s.sol => Connector-Nomad.s.sol} (60%) create mode 100644 script/Connector-XCM.s.sol create mode 100644 src/routers/nomad/Forwarder.sol create mode 100644 src/routers/xcm/Router.sol diff --git a/script/Connector.s.sol b/script/Connector-Nomad.s.sol similarity index 60% rename from script/Connector.s.sol rename to script/Connector-Nomad.s.sol index 7c09cbcd..2a81f65d 100644 --- a/script/Connector.s.sol +++ b/script/Connector-Nomad.s.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.7.6; -import { ConnectorRouter } from "src/routers/nomad/Router.sol"; +import { ConnectorNomadRouter } from "src/routers/nomad/Router.sol"; import { CentrifugeConnector } from "src/Connector.sol"; import { RestrictedTokenFactory, MemberlistFactory } from "src/token/factory.sol"; import "forge-std/Script.sol"; -contract ConnectorScript is Script { +contract ConnectorNomadScript is Script { function setUp() public {} function run() public { @@ -14,10 +14,9 @@ contract ConnectorScript is Script { address tokenFactory_ = address(new RestrictedTokenFactory()); address memberlistFactory_ = address(new MemberlistFactory()); - ConnectorRouter router = new ConnectorRouter(address(0)); + address connector = new CentrifugeConnector(tokenFactory_, memberlistFactory_); - new CentrifugeConnector(address(router), tokenFactory_, memberlistFactory_); - - // TODO: file connector on router + ConnectorRouter router = new ConnectorNomadRouter(connector); + connector.file("router", router); } } diff --git a/script/Connector-XCM.s.sol b/script/Connector-XCM.s.sol new file mode 100644 index 00000000..284a4f74 --- /dev/null +++ b/script/Connector-XCM.s.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.7.6; + +import { ConnectorXCMRouter } from "src/routers/xcm/Router.sol"; +import { CentrifugeConnector } from "src/Connector.sol"; +import { RestrictedTokenFactory, MemberlistFactory } from "src/token/factory.sol"; +import "forge-std/Script.sol"; + +contract ConnectorXCMScript is Script { + function setUp() public {} + + function run() public { + vm.broadcast(); + + address tokenFactory_ = address(new RestrictedTokenFactory()); + address memberlistFactory_ = address(new MemberlistFactory()); + address connector = new CentrifugeConnector(tokenFactory_, memberlistFactory_); + + // TODO: add centrifugeChainOrigin_ arg + ConnectorRouter router = new ConnectorXCMRouter(connector, address(0)); + connector.file("router", router); + } +} diff --git a/src/Connector.sol b/src/Connector.sol index 867543b9..2ac28307 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -22,16 +22,14 @@ contract CentrifugeConnector is Test { uint256 createdAt; } - mapping(uint64 => Pool) public pools; - struct Tranche { address token; uint256 latestPrice; // [ray] uint256 lastPriceUpdate; } + mapping(uint64 => Pool) public pools; mapping(uint64 => mapping(bytes16 => Tranche)) public tranches; - mapping(address => uint256) public wards; // --- Events --- @@ -41,8 +39,7 @@ contract CentrifugeConnector is Test { event PoolAdded(uint256 indexed poolId); event TrancheAdded(uint256 indexed poolId, bytes16 indexed trancheId, address indexed token); - constructor(address router_, address tokenFactory_, address memberlistFactory_) { - router = RouterLike(router_); + constructor(address tokenFactory_, address memberlistFactory_) { tokenFactory = RestrictedTokenFactoryLike(tokenFactory_); memberlistFactory = MemberlistFactoryLike(memberlistFactory_); wards[msg.sender] = 1; @@ -84,7 +81,7 @@ contract CentrifugeConnector is Test { emit PoolAdded(poolId); } - function addTranche(uint64 poolId, bytes16 trancheId) + function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public onlyRouter { @@ -94,9 +91,7 @@ contract CentrifugeConnector is Test { Tranche storage tranche = tranches[poolId][trancheId]; tranche.latestPrice = 1*10**27; - // Deploy restricted token - // TODO: set actual symbol and name - address token = tokenFactory.newRestrictedToken("SYMBOL", "Name"); + address token = tokenFactory.newRestrictedToken(tokenName, tokenSymbol); tranche.token = token; address memberlist = memberlistFactory.newMemberlist(); diff --git a/src/Messages.sol b/src/Messages.sol index ced1866f..94331e05 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -44,18 +44,45 @@ library ConnectorMessages { * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) * 9-25: trancheId (16 bytes) + * 26-58: tokenName (string = 32 bytes) + * 59-91: tokenSymbol (string = 32 bytes) */ - function formatAddTranche(uint64 poolId, bytes16 trancheId) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.AddTranche), poolId, trancheId); + function formatAddTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.AddTranche), poolId, trancheId, stringToBytes32(tokenName), stringToBytes32(tokenSymbol)); } function isAddTranche(bytes29 _msg) internal pure returns (bool) { return messageType(_msg) == Call.AddTranche; } - function parseAddTranche(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId) { + function parseAddTranche(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); + tokenName = bytes32ToString(bytes32(_msg.index(26, 32))); + tokenSymbol = bytes32ToString(bytes32(_msg.index(59, 32))); + } + + function stringToBytes32(string memory source) internal pure returns (bytes32 result) { + bytes memory tempEmptyStringTest = bytes(source); + if (tempEmptyStringTest.length == 0) { + return 0x0; + } + + assembly { + result := mload(add(source, 32)) + } + } + + function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) { + uint8 i = 0; + while(i < 32 && _bytes32[i] != 0) { + i++; + } + bytes memory bytesArray = new bytes(i); + for (i = 0; i < 32 && _bytes32[i] != 0; i++) { + bytesArray[i] = _bytes32[i]; + } + return string(bytesArray); } /** @@ -65,21 +92,23 @@ library ConnectorMessages { * 1-8: poolId (uint64 = 8 bytes) * 9-25: trancheId (16 bytes) * 26-46: user (Ethereum address, 20 bytes) - * 47-78: amount (uint256 = 32 bytes) + * 47-78: validUntil (uint256 = 32 bytes) + * + * TODO: use bytes32 for user (for non-EVM compatibility) */ - function formatUpdateMember(uint64 poolId, bytes16 trancheId, address user, uint256 amount) internal pure returns (bytes memory) { - return abi.encodePacked(uint8(Call.UpdateMember), poolId, trancheId, user, amount); + function formatUpdateMember(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.UpdateMember), poolId, trancheId, user, validUntil); } function isUpdateMember(bytes29 _msg) internal pure returns (bool) { return messageType(_msg) == Call.UpdateMember; } - function parseUpdateMember(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, address user, uint256 amount) { + function parseUpdateMember(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); user = address(bytes20(_msg.index(25, 20))); - amount = uint256(_msg.index(45, 32)); + validUntil = uint256(_msg.index(45, 32)); } /** @@ -88,7 +117,7 @@ library ConnectorMessages { * 0: call type (uint8 = 1 byte) * 1-8: poolId (uint64 = 8 bytes) * 9-25: trancheId (16 bytes) - * 26-58: amount (uint256 = 32 bytes) + * 26-58: price (uint256 = 32 bytes) */ function formatUpdateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) internal pure returns (bytes memory) { return abi.encodePacked(uint8(Call.UpdateTokenPrice), poolId, trancheId, price); diff --git a/src/routers/nomad/Forwarder.sol b/src/routers/nomad/Forwarder.sol new file mode 100644 index 00000000..e69de29b diff --git a/src/routers/nomad/Router.sol b/src/routers/nomad/Router.sol index fbdcfd4b..44412d91 100644 --- a/src/routers/nomad/Router.sol +++ b/src/routers/nomad/Router.sol @@ -9,12 +9,12 @@ import "forge-std/Test.sol"; interface ConnectorLike { function addPool(uint64 poolId) external; - function addTranche(uint64 poolId, bytes16 trancheId) external; + function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) external; function updateMember(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) external; function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) external; } -contract ConnectorRouter is Router, Test { +contract ConnectorNomadRouter is Router, Test { using TypedMemView for bytes; using TypedMemView for bytes29; using ConnectorMessages for bytes29; @@ -47,8 +47,8 @@ contract ConnectorRouter is Router, Test { uint64 poolId = ConnectorMessages.parseAddPool(_msg); connector.addPool(poolId); } else if (ConnectorMessages.isAddTranche(_msg) == true) { - (uint64 poolId, bytes16 trancheId) = ConnectorMessages.parseAddTranche(_msg); - connector.addTranche(poolId, trancheId); + (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) = ConnectorMessages.parseAddTranche(_msg); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol); } else if (ConnectorMessages.isUpdateMember(_msg) == true) { (uint64 poolId, bytes16 trancheId, address user, uint256 amount) = ConnectorMessages.parseUpdateMember(_msg); connector.updateMember(poolId, trancheId, user, amount); diff --git a/src/routers/xcm/Router.sol b/src/routers/xcm/Router.sol new file mode 100644 index 00000000..e52969c0 --- /dev/null +++ b/src/routers/xcm/Router.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.7.6; +pragma abicoder v2; + +import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol"; +import {Router} from "@nomad-xyz/contracts-router/contracts/Router.sol"; +import {ConnectorMessages} from "../..//Messages.sol"; +import "forge-std/Test.sol"; + +interface ConnectorLike { + function addPool(uint64 poolId) external; + function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) external; + function updateMember(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) external; + function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) external; +} + +contract ConnectorXCMRouter is Router, Test { + using TypedMemView for bytes; + using TypedMemView for bytes29; + using ConnectorMessages for bytes29; + + ConnectorLike public immutable connector; + + uint32 immutable centrifugeChainOrigin; + + constructor(address connector_, address centrifugeChainOrigin_) { + connector = ConnectorLike(connector_); + centrifugeChainOrigin = centrifugeChainOrigin_; + } + + modifier onlyCentrifugeChainOrigin() { + require(msg.sender == address(router), "ConnectorXCMRouter/invalid-origin"); + _; + } + + function handle( + uint32 _origin, + uint32 _nonce, + bytes32 _sender, + bytes memory _message + ) external override onlyCentrifugeChainOrigin { + bytes29 _msg = _message.ref(0); + if (ConnectorMessages.isAddPool(_msg) == true) { + uint64 poolId = ConnectorMessages.parseAddPool(_msg); + connector.addPool(poolId); + } else if (ConnectorMessages.isAddTranche(_msg) == true) { + (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) = ConnectorMessages.parseAddTranche(_msg); + connector.addTranche(poolId, trancheId, tokenName, tokenSymbol); + } else if (ConnectorMessages.isUpdateMember(_msg) == true) { + (uint64 poolId, bytes16 trancheId, address user, uint256 amount) = ConnectorMessages.parseUpdateMember(_msg); + connector.updateMember(poolId, trancheId, user, amount); + } else if (ConnectorMessages.isUpdateTokenPrice(_msg) == true) { + (uint64 poolId, bytes16 trancheId, uint256 price) = ConnectorMessages.parseUpdateTokenPrice(_msg); + connector.updateTokenPrice(poolId, trancheId, price); + } else { + require(false, "invalid-message"); + } + } +} diff --git a/src/token/erc20.sol b/src/token/erc20.sol index 28272c14..07a50f01 100644 --- a/src/token/erc20.sol +++ b/src/token/erc20.sol @@ -35,10 +35,10 @@ contract ERC20 { require((z = x - y) <= x, "math-sub-underflow"); } - constructor(string memory symbol_, string memory name_) { + constructor(string memory name_, string memory symbol_) { wards[msg.sender] = 1; - symbol = symbol_; name = name_; + symbol = symbol_; uint chainId; assembly { diff --git a/src/token/factory.sol b/src/token/factory.sol index fabe94b9..2c65b5cf 100644 --- a/src/token/factory.sol +++ b/src/token/factory.sol @@ -9,8 +9,8 @@ interface RestrictedTokenFactoryLike { } contract RestrictedTokenFactory { - function newRestrictedToken(string memory symbol, string memory name) public returns (address) { - RestrictedToken token = new RestrictedToken(symbol, name); + function newRestrictedToken(string memory name, string memory symbol) public returns (address) { + RestrictedToken token = new RestrictedToken(name, symbol); token.rely(msg.sender); token.deny(address(this)); return address(token); diff --git a/src/token/restricted.sol b/src/token/restricted.sol index a7d85c17..466e679d 100644 --- a/src/token/restricted.sol +++ b/src/token/restricted.sol @@ -10,6 +10,8 @@ interface MemberlistLike { interface ERC20Like { function mint(address usr, uint wad) external; + function name() external view returns (string memory); + function symbol() external view returns (string memory); } interface RestrictedTokenLike is ERC20Like { @@ -28,7 +30,7 @@ contract RestrictedToken is ERC20 { return memberlist.hasMember(usr); } - constructor(string memory symbol_, string memory name_) ERC20(symbol_, name_) {} + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {} function depend(bytes32 contractName, address addr) public auth { if (contractName == "memberlist") { memberlist = MemberlistLike(addr); } diff --git a/test/Connector.t.sol b/test/Connector.t.sol index 555b27da..65359384 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -38,24 +38,28 @@ contract ConnectorTest is Test { bridgedConnector.addPool(poolId); } - function testAddingSingleTrancheWorks(uint64 poolId, bytes16 trancheId) public { + function testAddingSingleTrancheWorks(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public { homeConnector.addPool(poolId); (uint64 actualPoolId,) = bridgedConnector.pools(poolId); assertEq(uint256(actualPoolId), uint256(poolId)); - homeConnector.addTranche(poolId, trancheId); - (address token, uint256 latestPrice,) = bridgedConnector.tranches(poolId, trancheId); + homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol); + (address token_, uint256 latestPrice,) = bridgedConnector.tranches(poolId, trancheId); assertTrue(latestPrice > 0); - assertTrue(token != address(0)); + assertTrue(token_ != address(0)); + + RestrictedTokenLike token = RestrictedTokenLike(token_); + assertEq(token.name(), tokenName); + assertEq(token.symbol(), tokenSymbol); } - function testAddingMultipleTranchesWorks(uint64 poolId, bytes16[] calldata trancheIds) public { + function testAddingMultipleTranchesWorks(uint64 poolId, bytes16[] calldata trancheIds, string memory tokenName, string memory tokenSymbol) public { vm.assume(trancheIds.length > 0 && trancheIds.length <= 5); homeConnector.addPool(poolId); for (uint i = 0; i < trancheIds.length; i++) { - homeConnector.addTranche(poolId, trancheIds[i]); + homeConnector.addTranche(poolId, trancheIds[i], tokenName, tokenSymbol); (address token, uint256 latestPrice,) = bridgedConnector.tranches(poolId, trancheIds[i]); assertTrue(latestPrice > 0); assertTrue(token != address(0)); @@ -107,7 +111,7 @@ contract ConnectorTest is Test { homeConnector.addPool(poolId); homeConnector.addTranche(poolId, trancheId); vm.expectRevert(bytes("CentrifugeConnector/not-the-router")); - homeConnector.updateTokenPrice(poolId, trancheId, price); + bridgedConnector.updateTokenPrice(poolId, trancheId, price); } function testUpdatingTokenPriceForNonExistentPoolFails(uint64 poolId) public { } diff --git a/test/mock/MockHomeConnector.sol b/test/mock/MockHomeConnector.sol index e58fcec7..f1c64efb 100644 --- a/test/mock/MockHomeConnector.sol +++ b/test/mock/MockHomeConnector.sol @@ -31,8 +31,8 @@ contract MockHomeConnector is Test { home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); } - function addTranche(uint64 poolId, bytes16 trancheId) public { - bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId); + function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public { + bytes memory _message = ConnectorMessages.formatAddTranche(poolId, trancheId, tokenName, tokenSymbol); home.handle(CENTRIFUGE_CHAIN_DOMAIN, NONCE, "1", _message); } From 2421246fc28a1c26fb08c957175c0aa4d8bc790a Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Thu, 30 Jun 2022 16:25:07 +0200 Subject: [PATCH 23/30] Fix adding tranche compilation --- script/Connector-Nomad.s.sol | 8 +++++--- script/Connector-XCM.s.sol | 7 ++++--- src/Messages.sol | 2 ++ src/routers/nomad/Router.sol | 3 ++- src/routers/xcm/Router.sol | 4 ++-- test/Connector.t.sol | 23 ++++++++++++----------- test/Messages.t.sol | 13 +++++++++---- 7 files changed, 36 insertions(+), 24 deletions(-) diff --git a/script/Connector-Nomad.s.sol b/script/Connector-Nomad.s.sol index 2a81f65d..ead86ab6 100644 --- a/script/Connector-Nomad.s.sol +++ b/script/Connector-Nomad.s.sol @@ -6,6 +6,7 @@ import { CentrifugeConnector } from "src/Connector.sol"; import { RestrictedTokenFactory, MemberlistFactory } from "src/token/factory.sol"; import "forge-std/Script.sol"; +// Script to deploy Connectors with a Nomad router. contract ConnectorNomadScript is Script { function setUp() public {} @@ -14,9 +15,10 @@ contract ConnectorNomadScript is Script { address tokenFactory_ = address(new RestrictedTokenFactory()); address memberlistFactory_ = address(new MemberlistFactory()); - address connector = new CentrifugeConnector(tokenFactory_, memberlistFactory_); + CentrifugeConnector connector = new CentrifugeConnector(tokenFactory_, memberlistFactory_); - ConnectorRouter router = new ConnectorNomadRouter(connector); - connector.file("router", router); + // TODO: pass _xAppConnectionManager as 2nd argument + ConnectorNomadRouter router = new ConnectorNomadRouter(address(connector), address(0)); + connector.file("router", address(router)); } } diff --git a/script/Connector-XCM.s.sol b/script/Connector-XCM.s.sol index 284a4f74..0eac9078 100644 --- a/script/Connector-XCM.s.sol +++ b/script/Connector-XCM.s.sol @@ -6,6 +6,7 @@ import { CentrifugeConnector } from "src/Connector.sol"; import { RestrictedTokenFactory, MemberlistFactory } from "src/token/factory.sol"; import "forge-std/Script.sol"; +// Script to deploy Connectors with an XCM router. contract ConnectorXCMScript is Script { function setUp() public {} @@ -14,10 +15,10 @@ contract ConnectorXCMScript is Script { address tokenFactory_ = address(new RestrictedTokenFactory()); address memberlistFactory_ = address(new MemberlistFactory()); - address connector = new CentrifugeConnector(tokenFactory_, memberlistFactory_); + CentrifugeConnector connector = new CentrifugeConnector(tokenFactory_, memberlistFactory_); // TODO: add centrifugeChainOrigin_ arg - ConnectorRouter router = new ConnectorXCMRouter(connector, address(0)); - connector.file("router", router); + ConnectorXCMRouter router = new ConnectorXCMRouter(address(connector), address(0)); + connector.file("router", address(router)); } } diff --git a/src/Messages.sol b/src/Messages.sol index 94331e05..5919edcc 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -62,6 +62,7 @@ library ConnectorMessages { tokenSymbol = bytes32ToString(bytes32(_msg.index(59, 32))); } + // TODO: should be moved to a util contract function stringToBytes32(string memory source) internal pure returns (bytes32 result) { bytes memory tempEmptyStringTest = bytes(source); if (tempEmptyStringTest.length == 0) { @@ -73,6 +74,7 @@ library ConnectorMessages { } } + // TODO: should be moved to a util contract function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) { uint8 i = 0; while(i < 32 && _bytes32[i] != 0) { diff --git a/src/routers/nomad/Router.sol b/src/routers/nomad/Router.sol index 44412d91..2bb1d4b9 100644 --- a/src/routers/nomad/Router.sol +++ b/src/routers/nomad/Router.sol @@ -23,8 +23,9 @@ contract ConnectorNomadRouter is Router, Test { uint32 immutable CENTRIFUGE_CHAIN_DOMAIN = 3000; - constructor(address connector_) { + constructor(address connector_, address _xAppConnectionManager) { connector = ConnectorLike(connector_); + __XAppConnectionClient_initialize(_xAppConnectionManager); } function send(bytes memory message) internal { diff --git a/src/routers/xcm/Router.sol b/src/routers/xcm/Router.sol index e52969c0..e3dd8f58 100644 --- a/src/routers/xcm/Router.sol +++ b/src/routers/xcm/Router.sol @@ -21,7 +21,7 @@ contract ConnectorXCMRouter is Router, Test { ConnectorLike public immutable connector; - uint32 immutable centrifugeChainOrigin; + address immutable centrifugeChainOrigin; constructor(address connector_, address centrifugeChainOrigin_) { connector = ConnectorLike(connector_); @@ -29,7 +29,7 @@ contract ConnectorXCMRouter is Router, Test { } modifier onlyCentrifugeChainOrigin() { - require(msg.sender == address(router), "ConnectorXCMRouter/invalid-origin"); + require(msg.sender == address(centrifugeChainOrigin), "ConnectorXCMRouter/invalid-origin"); _; } diff --git a/test/Connector.t.sol b/test/Connector.t.sol index 65359384..9c8ea217 100644 --- a/test/Connector.t.sol +++ b/test/Connector.t.sol @@ -7,21 +7,22 @@ import { RestrictedTokenFactory, MemberlistFactory } from "src/token/factory.sol import { RestrictedTokenLike } from "src/token/restricted.sol"; import { MemberlistLike } from "src/token/memberlist.sol"; import { MockHomeConnector } from "./mock/MockHomeConnector.sol"; -import { ConnectorRouter } from "src/routers/nomad/Router.sol"; +import { ConnectorNomadRouter } from "src/routers/nomad/Router.sol"; import "forge-std/Test.sol"; contract ConnectorTest is Test { CentrifugeConnector bridgedConnector; - ConnectorRouter bridgedRouter; + ConnectorNomadRouter bridgedRouter; MockHomeConnector homeConnector; function setUp() public { address tokenFactory_ = address(new RestrictedTokenFactory()); address memberlistFactory_ = address(new MemberlistFactory()); - bridgedConnector = new CentrifugeConnector(address(this), tokenFactory_, memberlistFactory_); - bridgedRouter = new ConnectorRouter(address(bridgedConnector)); + bridgedConnector = new CentrifugeConnector(tokenFactory_, memberlistFactory_); + // TODO: pass _xAppConnectionManager + bridgedRouter = new ConnectorNomadRouter(address(bridgedConnector), address(0)); bridgedConnector.file("router", address(bridgedRouter)); homeConnector = new MockHomeConnector(address(bridgedRouter)); @@ -66,15 +67,15 @@ contract ConnectorTest is Test { } } - function testAddingTranchesAsNonRouterFails(uint64 poolId, bytes16 trancheId) public { + function testAddingTranchesAsNonRouterFails(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public { homeConnector.addPool(poolId); vm.expectRevert(bytes("CentrifugeConnector/not-the-router")); - bridgedConnector.addTranche(poolId, trancheId); + bridgedConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol); } - function testAddingTranchesForNonExistentPoolFails(uint64 poolId, bytes16 trancheId) public { + function testAddingTranchesForNonExistentPoolFails(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public { vm.expectRevert(bytes("CentrifugeConnector/invalid-pool")); - homeConnector.addTranche(poolId, trancheId); + homeConnector.addTranche(poolId, trancheId, tokenName, tokenSymbol); } function testUpdatingMemberWorks(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) public { @@ -82,7 +83,7 @@ contract ConnectorTest is Test { vm.assume(user != address(0)); homeConnector.addPool(poolId); - homeConnector.addTranche(poolId, trancheId); + homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL"); homeConnector.updateMember(poolId, trancheId, user, validUntil); (address token_,,) = bridgedConnector.tranches(poolId, trancheId); @@ -99,7 +100,7 @@ contract ConnectorTest is Test { function testUpdatingTokenPriceWorks(uint64 poolId, bytes16 trancheId, uint256 price) public { homeConnector.addPool(poolId); - homeConnector.addTranche(poolId, trancheId); + homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL"); homeConnector.updateTokenPrice(poolId, trancheId, price); (, uint256 latestPrice, uint256 lastPriceUpdate) = bridgedConnector.tranches(poolId, trancheId); @@ -109,7 +110,7 @@ contract ConnectorTest is Test { function testUpdatingTokenPriceAsNonRouterFails(uint64 poolId, bytes16 trancheId, uint256 price) public { homeConnector.addPool(poolId); - homeConnector.addTranche(poolId, trancheId); + homeConnector.addTranche(poolId, trancheId, "Some Name", "SYMBOL"); vm.expectRevert(bytes("CentrifugeConnector/not-the-router")); bridgedConnector.updateTokenPrice(poolId, trancheId, price); diff --git a/test/Messages.t.sol b/test/Messages.t.sol index a3a4c8ac..06ce97d0 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -56,22 +56,26 @@ contract MessagesTest is Test { function testAddTrancheEncoding() public { assertEq( - ConnectorMessages.formatAddTranche(0, toBytes16(fromHex("100"))), + ConnectorMessages.formatAddTranche(0, toBytes16(fromHex("100")), "Some Name", "SYMBOL"), fromHex("010000000000000000") ); } - function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId) + function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public { bytes memory _message = ConnectorMessages.formatAddTranche( poolId, - trancheId + trancheId, + tokenName, + tokenSymbol ); - (uint64 decodedPoolId, bytes16 decodedTrancheId) = ConnectorMessages + (uint64 decodedPoolId, bytes16 decodedTrancheId, string memory decodedTokenName, string memory decodedTokenSymbol) = ConnectorMessages .parseAddTranche(_message.ref(0)); assertEq(uint256(decodedPoolId), uint256(poolId)); assertEq(decodedTrancheId, trancheId); + assertEq(decodedTokenName, tokenName); + assertEq(decodedTokenSymbol, tokenSymbol); } function testUpdateMemberEquivalence( @@ -154,4 +158,5 @@ contract MessagesTest is Test { } return fc; } + } From 7609584cba842604b96fcd369c29edac26a01d50 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 20 Jul 2022 15:28:12 +0100 Subject: [PATCH 24/30] add more encoding/decoding tests for Messages --- .gitmodules | 1 + src/Messages.sol | 5 ++-- src/routers/xcm/Router.sol | 1 + test/Messages.t.sol | 55 ++++++++++++++++++++++++++++++++------ 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/.gitmodules b/.gitmodules index 28158a3a..1fa31f34 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,6 +3,7 @@ url = https://github.com/foundry-rs/forge-std [submodule "lib/monorepo"] path = lib/monorepo + branch = main url = https://github.com/nomad-xyz/monorepo [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable diff --git a/src/Messages.sol b/src/Messages.sol index 5919edcc..211f2df6 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -3,6 +3,7 @@ pragma solidity ^0.7.6; import "@summa-tx/memview-sol/contracts/TypedMemView.sol"; +// library ConnectorMessages { using TypedMemView for bytes; using TypedMemView for bytes29; @@ -58,8 +59,8 @@ library ConnectorMessages { function parseAddTranche(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) { poolId = uint64(_msg.indexUint(1, 8)); trancheId = bytes16(_msg.index(9, 16)); - tokenName = bytes32ToString(bytes32(_msg.index(26, 32))); - tokenSymbol = bytes32ToString(bytes32(_msg.index(59, 32))); + tokenName = bytes32ToString(bytes32(_msg.index(25, 32))); + tokenSymbol = bytes32ToString(bytes32(_msg.index(57, 32))); } // TODO: should be moved to a util contract diff --git a/src/routers/xcm/Router.sol b/src/routers/xcm/Router.sol index e3dd8f58..7fd0b5bb 100644 --- a/src/routers/xcm/Router.sol +++ b/src/routers/xcm/Router.sol @@ -16,6 +16,7 @@ interface ConnectorLike { contract ConnectorXCMRouter is Router, Test { using TypedMemView for bytes; + // why bytes29? - https://github.com/summa-tx/memview-sol#why-bytes29 using TypedMemView for bytes29; using ConnectorMessages for bytes29; diff --git a/test/Messages.t.sol b/test/Messages.t.sol index 06ce97d0..7f1c8a41 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -4,6 +4,8 @@ pragma abicoder v2; import {TypedMemView} from "@summa-tx/memview-sol/contracts/TypedMemView.sol"; import {ConnectorMessages} from "src/Messages.sol"; +import "forge-std/console.sol"; +import "forge-std/console2.sol"; import "forge-std/Test.sol"; contract MessagesTest is Test { @@ -17,7 +19,7 @@ contract MessagesTest is Test { assertEq( ConnectorMessages.formatAddPool(0), fromHex("010000000000000000") - ); + ); assertEq( ConnectorMessages.formatAddPool(1), fromHex("010000000000000001") @@ -34,16 +36,16 @@ contract MessagesTest is Test { uint256(actualPoolId1), 0 ); - + uint64 actualPoolId2 = ConnectorMessages.parseAddPool(fromHex("010000000000000001").ref(0)); assertEq( - uint256(actualPoolId1), + uint256(actualPoolId2), 1 ); uint64 actualPoolId3 = ConnectorMessages.parseAddPool(fromHex("010000000000bce1a4").ref(0)); assertEq( - uint256(actualPoolId1), + uint256(actualPoolId3), 12378532 ); } @@ -56,14 +58,23 @@ contract MessagesTest is Test { function testAddTrancheEncoding() public { assertEq( - ConnectorMessages.formatAddTranche(0, toBytes16(fromHex("100")), "Some Name", "SYMBOL"), - fromHex("010000000000000000") + ConnectorMessages.formatAddTranche(0, toBytes16(fromHex("010000000000000064")), "Some Name", "SYMBOL"), + fromHex("02000000000000000000000000000000000000000000000009536f6d65204e616d65000000000000000000000000000000000000000000000053594d424f4c0000000000000000000000000000000000000000000000000000") ); } + function testAddTrancheDecoding() public returns (bytes memory) { + (uint64 decodedPoolId, bytes16 decodedTrancheId, string memory decodedTokenName, string memory decodedTokenSymbol) = ConnectorMessages.parseAddTranche(fromHex("02000000000000000000000000000000000000000000000009536f6d65204e616d65000000000000000000000000000000000000000000000053594d424f4c0000000000000000000000000000000000000000000000000000").ref(0)); + assertEq(uint(decodedPoolId), uint(0)); + assertEq(decodedTrancheId, toBytes16(fromHex("010000000000000064"))); + assertEq(decodedTokenName, "Some Name"); + assertEq(decodedTokenSymbol, "SYMBOL"); + } + function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) public { + // edge case token Symbol - not passing "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" bytes memory _message = ConnectorMessages.formatAddTranche( poolId, trancheId, @@ -78,6 +89,21 @@ contract MessagesTest is Test { assertEq(decodedTokenSymbol, tokenSymbol); } + function testUpdateMemberEncoding() public returns (bytes memory) { + assertEq( + ConnectorMessages.formatUpdateMember(5, toBytes16(fromHex("010000000000000003")), 0x225ef95fa90f4F7938A5b34234d14768cB4263dd, 1657870537), + fromHex("04000000000000000500000000000000000000000000000009225ef95fa90f4f7938a5b34234d14768cb4263dd0000000000000000000000000000000000000000000000000000000062d118c9") + ); + } + + function testUpdateMemberDecoding() public returns (bytes memory) { + (uint64 decodedPoolId, bytes16 decodedTrancheId, address decodedUser, uint256 decodedValidUntil) = ConnectorMessages.parseUpdateMember(fromHex("04000000000000000500000000000000000000000000000009225ef95fa90f4f7938a5b34234d14768cb4263dd0000000000000000000000000000000000000000000000000000000062d118c9").ref(0)); + assertEq(uint(decodedPoolId), uint(5)); + assertEq(decodedTrancheId, toBytes16(fromHex("010000000000000003"))); + assertEq(decodedUser, 0x225ef95fa90f4F7938A5b34234d14768cB4263dd); + assertEq(decodedValidUntil, uint(1657870537)); + } + function testUpdateMemberEquivalence( uint64 poolId, bytes16 trancheId, @@ -102,6 +128,20 @@ contract MessagesTest is Test { assertEq(decodedAmount, amount); } + function testUpdateTokenPriceEncoding() public returns (bytes memory) { + assertEq( + ConnectorMessages.formatUpdateTokenPrice(3, toBytes16(fromHex("010000000000000005")), 100), + fromHex("030000000000000003000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000064") + ); + } + + function testUpdateTokenPriceDecoding() public returns (bytes memory) { + (uint64 decodedPoolId, bytes16 decodedTrancheId, uint256 decodedPrice) = ConnectorMessages.parseUpdateTokenPrice(fromHex("030000000000000003000000000000000000000000000000090000000000000000000000000000000000000000000000000000000000000064").ref(0)); + assertEq(uint(decodedPoolId), uint(3)); + assertEq(decodedTrancheId, toBytes16(fromHex("010000000000000005"))); + assertEq(decodedPrice, uint(100)); + } + function testUpdateTokenPriceEquivalence( uint64 poolId, bytes16 trancheId, @@ -154,9 +194,8 @@ contract MessagesTest is Test { function toBytes16(bytes memory f) internal pure returns (bytes16 fc) { assembly { - fc := mload(add(f, 32)) + fc := mload(add(f, 16)) } return fc; } - } From 41a023764d3480b482052ee9eb430ddf123c18a3 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 20 Jul 2022 15:33:49 +0100 Subject: [PATCH 25/30] resolve conflict in gitmodules --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index f68d2a9a..1fa31f34 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,10 +3,7 @@ url = https://github.com/foundry-rs/forge-std [submodule "lib/monorepo"] path = lib/monorepo -<<<<<<< HEAD branch = main -======= ->>>>>>> origin/main url = https://github.com/nomad-xyz/monorepo [submodule "lib/openzeppelin-contracts-upgradeable"] path = lib/openzeppelin-contracts-upgradeable From 9d105c488b7e55652990eaa6d2a3febdbc00af11 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 20 Jul 2022 15:34:56 +0100 Subject: [PATCH 26/30] resolve comflicts in Messages tests --- test/Messages.t.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/Messages.t.sol b/test/Messages.t.sol index 8d01f805..7f1c8a41 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -45,11 +45,7 @@ contract MessagesTest is Test { uint64 actualPoolId3 = ConnectorMessages.parseAddPool(fromHex("010000000000bce1a4").ref(0)); assertEq( -<<<<<<< HEAD uint256(actualPoolId3), -======= - uint256(actualPoolId1), ->>>>>>> origin/main 12378532 ); } From ee54775f3e5cc5276ea8b98f9db8dafd7d7dbb37 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Wed, 20 Jul 2022 15:42:55 +0100 Subject: [PATCH 27/30] increase fuzz run count --- foundry.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/foundry.toml b/foundry.toml index b03c6a74..0a23c0bb 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,4 +3,5 @@ src = 'src' out = 'out' libs = ['lib'] +fuzz-runs = 1000 solc_version = "0.7.6" \ No newline at end of file From b0358a5c2328ce628c93f0f4fb8ef33cf071230e Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Thu, 21 Jul 2022 18:17:53 +0100 Subject: [PATCH 28/30] sign commit --- test/Messages.t.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Messages.t.sol b/test/Messages.t.sol index 7f1c8a41..30fdea63 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -67,8 +67,9 @@ contract MessagesTest is Test { (uint64 decodedPoolId, bytes16 decodedTrancheId, string memory decodedTokenName, string memory decodedTokenSymbol) = ConnectorMessages.parseAddTranche(fromHex("02000000000000000000000000000000000000000000000009536f6d65204e616d65000000000000000000000000000000000000000000000053594d424f4c0000000000000000000000000000000000000000000000000000").ref(0)); assertEq(uint(decodedPoolId), uint(0)); assertEq(decodedTrancheId, toBytes16(fromHex("010000000000000064"))); - assertEq(decodedTokenName, "Some Name"); + assertEq(decodedTokenName, "Some Name"); assertEq(decodedTokenSymbol, "SYMBOL"); + } function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) From 6f06bedcabcc88cdd5ab8d1cf0ca60649ce943e9 Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Thu, 21 Jul 2022 18:27:19 +0100 Subject: [PATCH 29/30] sign commit --- test/Messages.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Messages.t.sol b/test/Messages.t.sol index 30fdea63..914930e3 100644 --- a/test/Messages.t.sol +++ b/test/Messages.t.sol @@ -69,7 +69,6 @@ contract MessagesTest is Test { assertEq(decodedTrancheId, toBytes16(fromHex("010000000000000064"))); assertEq(decodedTokenName, "Some Name"); assertEq(decodedTokenSymbol, "SYMBOL"); - } function testAddTrancheEquivalence(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) From a347e22bee6235c75f234c1bdbae152913ae28fb Mon Sep 17 00:00:00 2001 From: ilinzweilin Date: Fri, 22 Jul 2022 14:24:33 +0100 Subject: [PATCH 30/30] add depost & withdraw functions --- src/Connector.sol | 15 ++++++++++- src/Messages.sol | 55 +++++++++++++++++++++++++++++++++++++- src/routers/xcm/Router.sol | 15 ++++++++--- src/token/restricted.sol | 5 +++- 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/src/Connector.sol b/src/Connector.sol index 2ac28307..25a6ce4a 100644 --- a/src/Connector.sol +++ b/src/Connector.sol @@ -96,6 +96,7 @@ contract CentrifugeConnector is Test { address memberlist = memberlistFactory.newMemberlist(); RestrictedTokenLike(token).depend("memberlist", memberlist); + MemberlistLike(memberlist).updateMember(address(this), uint(-1)); // required to be able to receive tokens in case of withdrawals emit TrancheAdded(poolId, trancheId, token); } @@ -121,7 +122,7 @@ contract CentrifugeConnector is Test { memberlist.updateMember(user, validUntil); } - function transferTo( + function deposit( uint64 poolId, bytes16 trancheId, address user, @@ -131,5 +132,17 @@ contract CentrifugeConnector is Test { require(token.hasMember(user), "CentrifugeConnector/not-a-member"); token.mint(user, amount); } + + function withdraw( + uint64 poolId, + bytes16 trancheId, + address user, + uint256 amount + ) public onlyRouter { + RestrictedTokenLike token = RestrictedTokenLike(tranches[poolId][trancheId].token); + require(token.balanceOf(user) >= amount, "CentrifugeConnector/insufficient-balance"); // optional + require(token.transferFrom(user, address(this), amount), "CentrifugeConnector/token-transfer-failed"); + token.burn(address(this), amount); + } } diff --git a/src/Messages.sol b/src/Messages.sol index 1f7d81cc..1fd3056f 100644 --- a/src/Messages.sol +++ b/src/Messages.sol @@ -13,7 +13,10 @@ library ConnectorMessages { AddTranche, UpdateTokenPrice, UpdateMember, - TransferTo + TransferTo, + Deposit, + Withdraw + } function messageType(bytes29 _msg) internal pure returns (Call _call) { @@ -135,4 +138,54 @@ library ConnectorMessages { price = uint256(_msg.index(25, 32)); } + + /** + * Deposit + * + * 0: call type (uint8 = 1 byte) + * 1-8: poolId (uint64 = 8 bytes) + * 9-25: trancheId (16 bytes) + * 26-46: user (Ethereum address, 20 bytes) + * 47-78: amount (uint256 = 32 bytes) + * + */ + function formatDeposit(uint64 poolId, bytes16 trancheId, address user, uint256 amount) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.Deposit), poolId, trancheId, user, amount); + } + + function isDeposit(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.Deposit; + } + + function parseDeposit(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, address user, uint256 amount) { + poolId = uint64(_msg.indexUint(1, 8)); + trancheId = bytes16(_msg.index(9, 16)); + user = address(bytes20(_msg.index(25, 20))); + amount = uint256(_msg.index(45, 32)); + } + + /** + * Withdraw + * + * 0: call type (uint8 = 1 byte) + * 1-8: poolId (uint64 = 8 bytes) + * 9-25: trancheId (16 bytes) + * 26-46: user (Ethereum address, 20 bytes) + * 47-78: amount (uint256 = 32 bytes) + * + */ + function formatWithdraw(uint64 poolId, bytes16 trancheId, address user, uint256 amount) internal pure returns (bytes memory) { + return abi.encodePacked(uint8(Call.Withdraw), poolId, trancheId, user, amount); + } + + function isWithdraw(bytes29 _msg) internal pure returns (bool) { + return messageType(_msg) == Call.Withdraw; + } + + function parseWithdraw(bytes29 _msg) internal pure returns (uint64 poolId, bytes16 trancheId, address user, uint256 amount) { + poolId = uint64(_msg.indexUint(1, 8)); + trancheId = bytes16(_msg.index(9, 16)); + user = address(bytes20(_msg.index(25, 20))); + amount = uint256(_msg.index(45, 32)); + } } \ No newline at end of file diff --git a/src/routers/xcm/Router.sol b/src/routers/xcm/Router.sol index 7fd0b5bb..5f57052a 100644 --- a/src/routers/xcm/Router.sol +++ b/src/routers/xcm/Router.sol @@ -12,6 +12,8 @@ interface ConnectorLike { function addTranche(uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) external; function updateMember(uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) external; function updateTokenPrice(uint64 poolId, bytes16 trancheId, uint256 price) external; + function deposit(uint64 poolId, bytes16 trancheId, address user, uint256 amount) external; + function withdraw(uint64 poolId, bytes16 trancheId, address user, uint256 amount) external; } contract ConnectorXCMRouter is Router, Test { @@ -48,12 +50,19 @@ contract ConnectorXCMRouter is Router, Test { (uint64 poolId, bytes16 trancheId, string memory tokenName, string memory tokenSymbol) = ConnectorMessages.parseAddTranche(_msg); connector.addTranche(poolId, trancheId, tokenName, tokenSymbol); } else if (ConnectorMessages.isUpdateMember(_msg) == true) { - (uint64 poolId, bytes16 trancheId, address user, uint256 amount) = ConnectorMessages.parseUpdateMember(_msg); - connector.updateMember(poolId, trancheId, user, amount); + (uint64 poolId, bytes16 trancheId, address user, uint256 validUntil) = ConnectorMessages.parseUpdateMember(_msg); + connector.updateMember(poolId, trancheId, user, validUntil); } else if (ConnectorMessages.isUpdateTokenPrice(_msg) == true) { (uint64 poolId, bytes16 trancheId, uint256 price) = ConnectorMessages.parseUpdateTokenPrice(_msg); connector.updateTokenPrice(poolId, trancheId, price); - } else { + } else if (ConnectorMessages.isDeposit(_msg) == true) { + (uint64 poolId, bytes16 trancheId, address user, uint256 amount) = ConnectorMessages.parseDeposit(_msg); + connector.deposit(poolId, trancheId, user, amount); + } else if (ConnectorMessages.isWithdraw(_msg) == true) { + (uint64 poolId, bytes16 trancheId, address user, uint256 amount) = ConnectorMessages.parseWithdraw(_msg); + connector.withdraw(poolId, trancheId, user, amount); + } + else { require(false, "invalid-message"); } } diff --git a/src/token/restricted.sol b/src/token/restricted.sol index 466e679d..9e24c858 100644 --- a/src/token/restricted.sol +++ b/src/token/restricted.sol @@ -12,12 +12,15 @@ interface ERC20Like { function mint(address usr, uint wad) external; function name() external view returns (string memory); function symbol() external view returns (string memory); + function balanceOf(address usr) external view returns (uint wad); + function burn(address usr, uint wad) external; + function transferFrom(address from, address to, uint amount) external returns (bool); } interface RestrictedTokenLike is ERC20Like { function memberlist() external view returns (address); function hasMember(address usr) external view returns (bool); - function depend(bytes32 contractName, address addr) external; + function depend(bytes32 contractName, address addr) external; } // Only mebmber with a valid (not expired) membership should be allowed to receive tokens