From bda3a8d2e40e3cb4459f37d6a96bcc23e760dfe6 Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Fri, 8 Dec 2023 17:42:58 +0000 Subject: [PATCH 01/11] (migration): start on migration of v1 tokens to v2 --- README.md | 4 +- src/circles/TemporalDiscount.sol | 2 +- src/graph/IGraph.sol | 7 --- src/migration/IHub.sol | 7 +++ src/migration/Migration.sol | 88 ++++++++++++++++++++++++++++++ test/graph/GraphPathTransfer.t.sol | 4 +- test/graph/MockHub.sol | 26 +++++++++ 7 files changed, 126 insertions(+), 12 deletions(-) create mode 100644 src/migration/Migration.sol diff --git a/README.md b/README.md index 755d269..63ee032 100644 --- a/README.md +++ b/README.md @@ -33,8 +33,8 @@ This Solidity project uses Foundry as a toolkit. If you don't have Foundry insta ### Using Foundry to build the contracts 1. First, you'll need to clone the repository to your local machine: ```bash - git clone https://github.com/CirclesUBI/time-circles-contracts - cd time-circles-contracts + git clone https://github.com/CirclesUBI/circles-contracts-v2 + cd circles-contracts-v2 ``` ### Compiling the contracts diff --git a/src/circles/TemporalDiscount.sol b/src/circles/TemporalDiscount.sol index 9c5f9c6..a35417b 100644 --- a/src/circles/TemporalDiscount.sol +++ b/src/circles/TemporalDiscount.sol @@ -29,7 +29,7 @@ contract TemporalDiscount is IERC20 { /** * EXA factor as 10^18 */ - uint256 internal constant EXA = uint256(1000000000000000000); + uint256 internal constant EXA = uint256(10 ** 18); /** * Store the signed 128-bit 64.64 representation of 1 as a constant diff --git a/src/graph/IGraph.sol b/src/graph/IGraph.sol index 9290d1e..461a61c 100644 --- a/src/graph/IGraph.sol +++ b/src/graph/IGraph.sol @@ -4,17 +4,10 @@ pragma solidity >=0.8.13; import "./ICircleNode.sol"; interface IGraph { - // function trust(address _avatar) external; - // function untrust(address _avatar) external; - function checkAllAreTrustedCircleNodes(address group, ICircleNode[] calldata circles, bool includeGroups) external view returns (bool allTrusted_); function fetchAllocation(address _avatar) external returns (int128 allocation_, uint256 earliestTimestamp_); - - // function checkAncestorMigrations(address _avatar) - // external - // returns (bool objectToStartMint_, address[] memory migrationTokens_); } diff --git a/src/migration/IHub.sol b/src/migration/IHub.sol index d21fd1f..6abdc5d 100644 --- a/src/migration/IHub.sol +++ b/src/migration/IHub.sol @@ -15,4 +15,11 @@ interface IHubV1 { function limits(address truster, address trustee) external returns (uint256); function trust(address trustee, uint256 limit) external; + + function deployedAt() external returns (uint256); + function initialIssuance() external returns (uint256); + function inflation() external returns (uint256); + function divisor() external returns (uint256); + function period() external returns (uint256); + function periods() external returns (uint256); } diff --git a/src/migration/Migration.sol b/src/migration/Migration.sol new file mode 100644 index 0000000..383d383 --- /dev/null +++ b/src/migration/Migration.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.13; + +import "./IHub.sol"; +import "./IToken.sol"; +import "../graph/IGraph.sol"; + +contract CirclesMigration { + // State variables + + IHubV1 public immutable hubV1; + + IGraph public immutable graphV2; + + /** + * @notice For conversion of the inflationary Circles of v1 + * to the demurraged v2 Circles, we need to compute the equivalent + * reduction factor + */ + int128 public immutable inflationContinuous64x64; + + // uint256 public immutable + + // Constructor + + constructor(IHubV1 _hubV1, IGraph _graphV2) { + require(address(_hubV1) != address(0), "Hub v1 address can not be zero."); + require(address(_graphV2) != address(0), "Graph v2 address can not be zero."); + + hubV1 = _hubV1; + graphV2 = _graphV2; + } + + // External functions + + /** + * @param _depositAmount Deposit amount specifies the amount of inflationary + * hub v1 circles the caller wants to convert and migrate to demurraged Circles. + * One can only convert personal v1 Circles, if that person has stopped their v1 + * circles contract, and has created a v2 demurraged Circles contract by registering in v2. + */ + function convertAndMigrateCircles(ITokenV1 _originCircle, uint256 _depositAmount) + external + returns (uint256 mintedAmount_) + { + // first check the existance of the origin Circle and whether it is stopped + require(checkOriginCircleStopped(_originCircle), "Origin Circle must have been stopped before conversion."); + + // calculate inflationary correction + } + + // Public functions + + function checkOriginCircleStopped(ITokenV1 _originCircle) public returns (bool stopped_) { + require(hubV1.tokenToUser(address(_originCircle)) != address(0), "Origin Circle is not registered in hub V1."); + + return stopped_ = _originCircle.stopped(); + } + + // Private functions + + function calculateEquivalentReductionFactor() private returns (int128 reductionFactor_) { + // to calculate the reduction factor, first we need to get the params + + // from deployed v1 contract SHOULD return inflation = 107 + uint256 inflation = hubV1.inflation(); + // from deployed v1 contract SHOULD return divisor = 100 + uint256 divisor = hubV1.divisor(); + // from deployed v1 contract SHOULD return deployedAt = 1602786330 + // (for reference 6:25:30 pm UTC | Thursday, October 15, 2020) + uint256 deployedAt = hubV1.deployedAt(); + // from deployed v1 contract SHOULD return initialIssuance = 92592592592592 + // (equivalent to 1/3 CRC per hour; original at launch 8 CRC per day) + // later it was decided that 24 CRC per day, or 1 CRC per hour should be the standard gauge + // and the correction was done at the interface level, so everyone sees their balance + // corrected for 24 CRC/day; we should hence adopt this correction in the token migration step. + uint256 initialIssuance = hubV1.initialIssuance(); + // from deployed v1 contract SHOULD return period = 31556952 + // (equivalent to 365 days 5 hours 49 minutes 12 seconds) + // because the period is not a whole number of hours, + // the interval of hub v1 will not match the periodicity of any hour-based period in v2. + uint256 period = hubV1.period(); + + + + + } +} diff --git a/test/graph/GraphPathTransfer.t.sol b/test/graph/GraphPathTransfer.t.sol index d277c1d..6d1529e 100644 --- a/test/graph/GraphPathTransfer.t.sol +++ b/test/graph/GraphPathTransfer.t.sol @@ -68,12 +68,12 @@ contract GraphPathTransferTest is Test, TimeSetup { } // skip time so that all participants can claim an issuance - skipTime(2 days + 1 minutes); + skipTime(52 weeks + 1 minutes); for (uint256 i = 0; i < N; i++) { // vm.prank(addresses[i]); circleNodes[i].claimIssuance(); - assertEq(circleNodes[i].balanceOf(addresses[i]), 48 * TIC); + assertEq(circleNodes[i].balanceOf(addresses[i]), 240 * TIC); } // to build a correct flow matrix, we need to present the vertices diff --git a/test/graph/MockHub.sol b/test/graph/MockHub.sol index e006e16..24874ae 100644 --- a/test/graph/MockHub.sol +++ b/test/graph/MockHub.sol @@ -32,6 +32,32 @@ contract MockHubV1 is IHubV1 { notMocked(); } + // parameters taken from: + // https://gnosisscan.io/address/0x29b9a7fbb8995b2423a71cc17cf9810798f6c543/advanced#readContract + function deployedAt() public returns (uint256) { + return uint256(1602786330); + } + + function initialIssuance() public returns (uint256) { + return uint256(92592592592592); + } + + function inflation() public returns (uint256) { + return uint256(107); + } + + function divisor() public returns (uint256) { + return uint256(100); + } + + function period() public returns (uint256) { + return uint256(31556952); + } + + function periods() public returns (uint256) { + return (block.timestamp - deployedAt()) / period(); + } + function notMocked() private pure { assert(false); } From c4f354938992f9b3329dfa05d09ae34669529449 Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Sat, 9 Dec 2023 19:49:28 +0000 Subject: [PATCH 02/11] (migration): follow linear interpolation for conversion instead --- src/migration/IHub.sol | 2 ++ src/migration/Migration.sol | 66 ++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 30 deletions(-) diff --git a/src/migration/IHub.sol b/src/migration/IHub.sol index 6abdc5d..90def71 100644 --- a/src/migration/IHub.sol +++ b/src/migration/IHub.sol @@ -18,6 +18,8 @@ interface IHubV1 { function deployedAt() external returns (uint256); function initialIssuance() external returns (uint256); + function issuance() external returns (uint256); + function issuanceByStep(uint256 _periods) external returns (uint256); function inflation() external returns (uint256); function divisor() external returns (uint256); function period() external returns (uint256); diff --git a/src/migration/Migration.sol b/src/migration/Migration.sol index 383d383..7aa459e 100644 --- a/src/migration/Migration.sol +++ b/src/migration/Migration.sol @@ -12,23 +12,44 @@ contract CirclesMigration { IGraph public immutable graphV2; - /** - * @notice For conversion of the inflationary Circles of v1 - * to the demurraged v2 Circles, we need to compute the equivalent - * reduction factor - */ - int128 public immutable inflationContinuous64x64; - - // uint256 public immutable + uint256 public immutable inflation; + uint256 public immutable divisor; + uint256 public immutable deployedAt; + uint256 public immutable initialIssuance; + uint256 public immutable period; // Constructor + // see for context prior discussions on the conversion of CRC to TC, + // and some reference to the 8 CRC per day to 24 CRC per day gauge-reset + // https://aboutcircles.com/t/conversion-from-crc-to-time-circles-and-back/463 + // the UI conversion used is found here: + // https://github.com/circlesland/timecircle/blob/master/src/index.ts constructor(IHubV1 _hubV1, IGraph _graphV2) { require(address(_hubV1) != address(0), "Hub v1 address can not be zero."); require(address(_graphV2) != address(0), "Graph v2 address can not be zero."); hubV1 = _hubV1; graphV2 = _graphV2; + + // from deployed v1 contract SHOULD return inflation = 107 + inflation = hubV1.inflation(); + // from deployed v1 contract SHOULD return divisor = 100 + divisor = hubV1.divisor(); + // from deployed v1 contract SHOULD return deployedAt = 1602786330 + // (for reference 6:25:30 pm UTC | Thursday, October 15, 2020) + deployedAt = hubV1.deployedAt(); + // from deployed v1 contract SHOULD return initialIssuance = 92592592592592 + // (equivalent to 1/3 CRC per hour; original at launch 8 CRC per day) + // later it was decided that 24 CRC per day, or 1 CRC per hour should be the standard gauge + // and the correction was done at the interface level, so everyone sees their balance + // corrected for 24 CRC/day; we should hence adopt this correction in the token migration step. + initialIssuance = hubV1.initialIssuance(); + // from deployed v1 contract SHOULD return period = 31556952 + // (equivalent to 365 days 5 hours 49 minutes 12 seconds) + // because the period is not a whole number of hours, + // the interval of hub v1 will not match the periodicity of any hour-based period in v2. + period = hubV1.period(); } // External functions @@ -59,30 +80,15 @@ contract CirclesMigration { // Private functions - function calculateEquivalentReductionFactor() private returns (int128 reductionFactor_) { - // to calculate the reduction factor, first we need to get the params + function convertFromV1ToTimeCircles(uint256 _amount) private returns (uint256 timeCircleAmount_) { + uint256 currentPeriod = hubV1.periods(); + uint256 nextPeriod = currentPeriod + 1; - // from deployed v1 contract SHOULD return inflation = 107 - uint256 inflation = hubV1.inflation(); - // from deployed v1 contract SHOULD return divisor = 100 - uint256 divisor = hubV1.divisor(); - // from deployed v1 contract SHOULD return deployedAt = 1602786330 - // (for reference 6:25:30 pm UTC | Thursday, October 15, 2020) - uint256 deployedAt = hubV1.deployedAt(); - // from deployed v1 contract SHOULD return initialIssuance = 92592592592592 - // (equivalent to 1/3 CRC per hour; original at launch 8 CRC per day) - // later it was decided that 24 CRC per day, or 1 CRC per hour should be the standard gauge - // and the correction was done at the interface level, so everyone sees their balance - // corrected for 24 CRC/day; we should hence adopt this correction in the token migration step. - uint256 initialIssuance = hubV1.initialIssuance(); - // from deployed v1 contract SHOULD return period = 31556952 - // (equivalent to 365 days 5 hours 49 minutes 12 seconds) - // because the period is not a whole number of hours, - // the interval of hub v1 will not match the periodicity of any hour-based period in v2. - uint256 period = hubV1.period(); - + uint256 startOfPeriod = hubV1.deployedAt() + currentPeriod * hubV1.period(); - + // number of seconds into the new period + uint256 secondsIntoCurrentPeriod = block.timestamp - startOfPeriod; + } } From d7ed2fd41756da4d798f49dfdaa519079fc98db5 Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Mon, 11 Dec 2023 17:09:02 +0000 Subject: [PATCH 03/11] (migration): convert v1 to v2 with linear interpolation --- src/migration/IHub.sol | 5 +++-- src/migration/Migration.sol | 34 ++++++++++++++++++++++++++++---- test/graph/MockHub.sol | 39 +++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 6 deletions(-) diff --git a/src/migration/IHub.sol b/src/migration/IHub.sol index 90def71..4773ace 100644 --- a/src/migration/IHub.sol +++ b/src/migration/IHub.sol @@ -18,8 +18,9 @@ interface IHubV1 { function deployedAt() external returns (uint256); function initialIssuance() external returns (uint256); - function issuance() external returns (uint256); - function issuanceByStep(uint256 _periods) external returns (uint256); + // function issuance() external returns (uint256); + // function issuanceByStep(uint256 periods) external returns (uint256); + function inflate(uint256 initial, uint256 periods) external returns (uint256); function inflation() external returns (uint256); function divisor() external returns (uint256); function period() external returns (uint256); diff --git a/src/migration/Migration.sol b/src/migration/Migration.sol index 7aa459e..9f51e5b 100644 --- a/src/migration/Migration.sol +++ b/src/migration/Migration.sol @@ -6,6 +6,10 @@ import "./IToken.sol"; import "../graph/IGraph.sol"; contract CirclesMigration { + // Constant + + uint256 private constant ACCURACY = uint256(10**8); + // State variables IHubV1 public immutable hubV1; @@ -78,17 +82,39 @@ contract CirclesMigration { return stopped_ = _originCircle.stopped(); } - // Private functions - - function convertFromV1ToTimeCircles(uint256 _amount) private returns (uint256 timeCircleAmount_) { + function convertFromV1ToTimeCircles(uint256 _amount) public returns (uint256 timeCircleAmount_) { uint256 currentPeriod = hubV1.periods(); uint256 nextPeriod = currentPeriod + 1; - uint256 startOfPeriod = hubV1.deployedAt() + currentPeriod * hubV1.period(); + uint256 startOfPeriod = deployedAt + currentPeriod * period; // number of seconds into the new period uint256 secondsIntoCurrentPeriod = block.timestamp - startOfPeriod; + // rather than using initial issuance; use a clean order of magnitude + // to calculate the conversion factor. + // This is because initial issuance (originally ~ 8 CRC / day; + // then corrected to 24 CRC / day) is ever so slightly less than 1 CRC / hour + // ( 0.9999999999999936 CRC / hour to be precise ) + // but if we later divide by this, then the error is ever so slightly + // in favor of converting - note there are many more errors, + // but we try to have each error always be in disadvantage of v1 so that + // there is no adverse incentive to mint and convert from v1 + uint256 factorCurrentPeriod = hubV1.inflate(ACCURACY, currentPeriod); + uint256 factorNextPeriod = hubV1.inflate(ACCURACY, nextPeriod); + + // linear interpolation of inflation rate + // r = x * (1 - a) + y * a + // if a = secondsIntoCurrentPeriod / Period = s / P + // => P * r = x * (P - s) + y * s + uint256 rP = factorCurrentPeriod * (period - secondsIntoCurrentPeriod) + + factorNextPeriod * secondsIntoCurrentPeriod; + // account for the adjustment of the accepted gauge of 24 CRC / day, + // rather than 8 CRC / day, so multiply by 3 + // and divide by the inflation rate to convert to temporally discounted units + // (as if inflation would have been continuously adjusted. This is not the case, + // it is only annually compounded, but the disadvantage is for v1 vs v2). + return timeCircleAmount_ = (_amount * 3 * ACCURACY * period) / rP; } } diff --git a/test/graph/MockHub.sol b/test/graph/MockHub.sol index 24874ae..2ec6d99 100644 --- a/test/graph/MockHub.sol +++ b/test/graph/MockHub.sol @@ -42,6 +42,15 @@ contract MockHubV1 is IHubV1 { return uint256(92592592592592); } + function inflate(uint256 _initial, uint256 _periods) external returns (uint256) { + // copy of the implementation from circles contracts v1 + // to mirror the same numerical errors as hub v1 has. + // https://github.com/CirclesUBI/circles-contracts/blob/master/contracts/Hub.sol#L96-L103 + uint256 q = pow(inflation(), _periods); + uint256 d = pow(divisor(), _periods); + return (_initial * q) / d; + } + function inflation() public returns (uint256) { return uint256(107); } @@ -58,7 +67,37 @@ contract MockHubV1 is IHubV1 { return (block.timestamp - deployedAt()) / period(); } + // Private functions + function notMocked() private pure { assert(false); } + + /// @dev this is an implementation of exponentiation by squares + /// @param base the base to be used in the calculation + /// @param exponent the exponent to be used in the calculation + /// @return the result of the calculation + function pow(uint256 base, uint256 exponent) public pure returns (uint256) { + if (base == 0) { + return 0; + } + if (exponent == 0) { + return 1; + } + if (exponent == 1) { + return base; + } + uint256 y = 1; + while(exponent > 1) { + if(exponent % 2 == 0) { + base = base * base; + exponent = exponent / 2; + } else { + y = base * y; + base = base * base; + exponent = (exponent - 1) / 2; + } + } + return base * y; + } } From ee5d847205d7611709bb5e37b6bc34af9f78b11b Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Mon, 11 Dec 2023 18:58:49 +0000 Subject: [PATCH 04/11] (migration): write crude first test for conversion between v1 and time circles --- src/migration/Migration.sol | 16 ++++---- test/graph/Graph.t.sol | 2 +- test/graph/GraphPathTransfer.t.sol | 2 +- test/migration/Migration.t.sol | 53 +++++++++++++++++++++++++++ test/{graph => migration}/MockHub.sol | 4 +- 5 files changed, 65 insertions(+), 12 deletions(-) create mode 100644 test/migration/Migration.t.sol rename test/{graph => migration}/MockHub.sol (97%) diff --git a/src/migration/Migration.sol b/src/migration/Migration.sol index 9f51e5b..848f819 100644 --- a/src/migration/Migration.sol +++ b/src/migration/Migration.sol @@ -8,13 +8,13 @@ import "../graph/IGraph.sol"; contract CirclesMigration { // Constant - uint256 private constant ACCURACY = uint256(10**8); + uint256 private constant ACCURACY = uint256(10 ** 8); // State variables IHubV1 public immutable hubV1; - IGraph public immutable graphV2; + // IGraph public immutable graphV2; uint256 public immutable inflation; uint256 public immutable divisor; @@ -29,12 +29,12 @@ contract CirclesMigration { // https://aboutcircles.com/t/conversion-from-crc-to-time-circles-and-back/463 // the UI conversion used is found here: // https://github.com/circlesland/timecircle/blob/master/src/index.ts - constructor(IHubV1 _hubV1, IGraph _graphV2) { + constructor(IHubV1 _hubV1) { require(address(_hubV1) != address(0), "Hub v1 address can not be zero."); - require(address(_graphV2) != address(0), "Graph v2 address can not be zero."); + // require(address(_graphV2) != address(0), "Graph v2 address can not be zero."); hubV1 = _hubV1; - graphV2 = _graphV2; + // graphV2 = _graphV2; // from deployed v1 contract SHOULD return inflation = 107 inflation = hubV1.inflation(); @@ -107,9 +107,9 @@ contract CirclesMigration { // r = x * (1 - a) + y * a // if a = secondsIntoCurrentPeriod / Period = s / P // => P * r = x * (P - s) + y * s - uint256 rP = factorCurrentPeriod * (period - secondsIntoCurrentPeriod) - + factorNextPeriod * secondsIntoCurrentPeriod; - + uint256 rP = + factorCurrentPeriod * (period - secondsIntoCurrentPeriod) + factorNextPeriod * secondsIntoCurrentPeriod; + // account for the adjustment of the accepted gauge of 24 CRC / day, // rather than 8 CRC / day, so multiply by 3 // and divide by the inflation rate to convert to temporally discounted units diff --git a/test/graph/Graph.t.sol b/test/graph/Graph.t.sol index ec8bac6..3a01905 100644 --- a/test/graph/Graph.t.sol +++ b/test/graph/Graph.t.sol @@ -8,7 +8,7 @@ import "../../src/graph/ICircleNode.sol"; import "../../src/circles/TimeCircle.sol"; import "../../src/circles/GroupCircle.sol"; import "../../src/mint/MintSplitter.sol"; -import "./MockHub.sol"; +import "../migration/MockHub.sol"; import "./MockInternalGraph.sol"; contract GraphTest is Test { diff --git a/test/graph/GraphPathTransfer.t.sol b/test/graph/GraphPathTransfer.t.sol index 6d1529e..ca0f645 100644 --- a/test/graph/GraphPathTransfer.t.sol +++ b/test/graph/GraphPathTransfer.t.sol @@ -8,7 +8,7 @@ import "../../src/circles/TimeCircle.sol"; import "../../src/circles/GroupCircle.sol"; import "../../src/mint/MintSplitter.sol"; import "../setup/TimeSetup.sol"; -import "./MockHub.sol"; +import "../migration/MockHub.sol"; contract GraphPathTransferTest is Test, TimeSetup { // Constant diff --git a/test/migration/Migration.t.sol b/test/migration/Migration.t.sol new file mode 100644 index 0000000..79812e3 --- /dev/null +++ b/test/migration/Migration.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {StdCheats} from "forge-std/StdCheats.sol"; +import "../../src/migration/Migration.sol"; +import "../setup/TimeSetup.sol"; +import "./MockHub.sol"; + +contract GraphTest is Test { + // Constants + + uint256 private ACCURACY_ONE = uint256(10 ** 8); + uint256 private ACCURACY_ONE_HUNDREDTH = uint256(10 ** 6); + uint256 private MOMENT_IN_TIME = uint256(1702317618); + + // State variables + + MockHubV1 public mockHubV1; + + CirclesMigration public migration; + + function setUp() public { + mockHubV1 = new MockHubV1(); + + migration = new CirclesMigration(mockHubV1); + + vm.warp(MOMENT_IN_TIME); + } + + function testConversionMigrationV1ToTimeCircles() public { + // `MOMENT_IN_TIME` is in the third period + assertEq(uint256(3), mockHubV1.periods()); + + uint256 originalAmountV1 = uint256(7471193061687490000000); + // at the constant `MOMENT_IN_TIME` + uint256 expectedAmountV2 = uint256(1809845 * 10 ** 16); + + // this is a crude first test. These tests should be redone more accurately + // possibly even on-chain + + // for now require accuracy < 1% + uint256 convertedAmount = migration.convertFromV1ToTimeCircles(originalAmountV1); + uint256 difference = uint256(0); + if (convertedAmount < expectedAmountV2) { + difference = ACCURACY_ONE - (ACCURACY_ONE * convertedAmount) / expectedAmountV2; + } else { + difference = ACCURACY_ONE - (ACCURACY_ONE * expectedAmountV2) / convertedAmount; + } + uint256 relativeDifference = difference / ACCURACY_ONE_HUNDREDTH; + assertEq(0, relativeDifference); + } +} diff --git a/test/graph/MockHub.sol b/test/migration/MockHub.sol similarity index 97% rename from test/graph/MockHub.sol rename to test/migration/MockHub.sol index 2ec6d99..de6cb59 100644 --- a/test/graph/MockHub.sol +++ b/test/migration/MockHub.sol @@ -88,8 +88,8 @@ contract MockHubV1 is IHubV1 { return base; } uint256 y = 1; - while(exponent > 1) { - if(exponent % 2 == 0) { + while (exponent > 1) { + if (exponent % 2 == 0) { base = base * base; exponent = exponent / 2; } else { From d8056f559bb35311fdd6eb0ab30b089b87f318cf Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Mon, 11 Dec 2023 19:06:54 +0000 Subject: [PATCH 05/11] (tests): undo experiments with path transfer test; follow up in issue #47 --- test/graph/GraphPathTransfer.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/graph/GraphPathTransfer.t.sol b/test/graph/GraphPathTransfer.t.sol index ca0f645..ec5215a 100644 --- a/test/graph/GraphPathTransfer.t.sol +++ b/test/graph/GraphPathTransfer.t.sol @@ -68,12 +68,12 @@ contract GraphPathTransferTest is Test, TimeSetup { } // skip time so that all participants can claim an issuance - skipTime(52 weeks + 1 minutes); + skipTime(2 days + 1 minutes); for (uint256 i = 0; i < N; i++) { // vm.prank(addresses[i]); circleNodes[i].claimIssuance(); - assertEq(circleNodes[i].balanceOf(addresses[i]), 240 * TIC); + assertEq(circleNodes[i].balanceOf(addresses[i]), 48 * TIC); } // to build a correct flow matrix, we need to present the vertices From 273cab5d9f35abae5dff811bf80db2be2947bf99 Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Tue, 12 Dec 2023 13:28:32 +0000 Subject: [PATCH 06/11] (IHub): improve visibility of Hub v1 interface --- src/migration/IHub.sol | 18 +++++++++--------- src/migration/Migration.sol | 23 ++++++++++++----------- test/migration/MockHub.sol | 14 +++++++------- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/migration/IHub.sol b/src/migration/IHub.sol index 4773ace..27f4889 100644 --- a/src/migration/IHub.sol +++ b/src/migration/IHub.sol @@ -16,13 +16,13 @@ interface IHubV1 { function trust(address trustee, uint256 limit) external; - function deployedAt() external returns (uint256); - function initialIssuance() external returns (uint256); - // function issuance() external returns (uint256); - // function issuanceByStep(uint256 periods) external returns (uint256); - function inflate(uint256 initial, uint256 periods) external returns (uint256); - function inflation() external returns (uint256); - function divisor() external returns (uint256); - function period() external returns (uint256); - function periods() external returns (uint256); + function deployedAt() external view returns (uint256); + function initialIssuance() external view returns (uint256); + // function issuance() external view returns (uint256); + // function issuanceByStep(uint256 periods) external view returns (uint256); + function inflate(uint256 initial, uint256 periods) external view returns (uint256); + function inflation() external view returns (uint256); + function divisor() external view returns (uint256); + function period() external view returns (uint256); + function periods() external view returns (uint256); } diff --git a/src/migration/Migration.sol b/src/migration/Migration.sol index 848f819..809e84e 100644 --- a/src/migration/Migration.sol +++ b/src/migration/Migration.sol @@ -31,29 +31,30 @@ contract CirclesMigration { // https://github.com/circlesland/timecircle/blob/master/src/index.ts constructor(IHubV1 _hubV1) { require(address(_hubV1) != address(0), "Hub v1 address can not be zero."); - // require(address(_graphV2) != address(0), "Graph v2 address can not be zero."); hubV1 = _hubV1; - // graphV2 = _graphV2; + + // from deployed v1 contract SHOULD return deployedAt = 1602786330 + // (for reference 6:25:30 pm UTC | Thursday, October 15, 2020) + deployedAt = hubV1.deployedAt(); + // from deployed v1 contract SHOULD return period = 31556952 + // (equivalent to 365 days 5 hours 49 minutes 12 seconds) + // because the period is not a whole number of hours, + // the interval of hub v1 will not match the periodicity of any hour-based period in v2. + period = hubV1.period(); + + // note: currently these parameters are not used, remove them if they remain so // from deployed v1 contract SHOULD return inflation = 107 inflation = hubV1.inflation(); // from deployed v1 contract SHOULD return divisor = 100 divisor = hubV1.divisor(); - // from deployed v1 contract SHOULD return deployedAt = 1602786330 - // (for reference 6:25:30 pm UTC | Thursday, October 15, 2020) - deployedAt = hubV1.deployedAt(); // from deployed v1 contract SHOULD return initialIssuance = 92592592592592 // (equivalent to 1/3 CRC per hour; original at launch 8 CRC per day) // later it was decided that 24 CRC per day, or 1 CRC per hour should be the standard gauge // and the correction was done at the interface level, so everyone sees their balance // corrected for 24 CRC/day; we should hence adopt this correction in the token migration step. initialIssuance = hubV1.initialIssuance(); - // from deployed v1 contract SHOULD return period = 31556952 - // (equivalent to 365 days 5 hours 49 minutes 12 seconds) - // because the period is not a whole number of hours, - // the interval of hub v1 will not match the periodicity of any hour-based period in v2. - period = hubV1.period(); } // External functions @@ -82,7 +83,7 @@ contract CirclesMigration { return stopped_ = _originCircle.stopped(); } - function convertFromV1ToTimeCircles(uint256 _amount) public returns (uint256 timeCircleAmount_) { + function convertFromV1ToTimeCircles(uint256 _amount) public view returns (uint256 timeCircleAmount_) { uint256 currentPeriod = hubV1.periods(); uint256 nextPeriod = currentPeriod + 1; diff --git a/test/migration/MockHub.sol b/test/migration/MockHub.sol index de6cb59..7244bfa 100644 --- a/test/migration/MockHub.sol +++ b/test/migration/MockHub.sol @@ -34,15 +34,15 @@ contract MockHubV1 is IHubV1 { // parameters taken from: // https://gnosisscan.io/address/0x29b9a7fbb8995b2423a71cc17cf9810798f6c543/advanced#readContract - function deployedAt() public returns (uint256) { + function deployedAt() public pure returns (uint256) { return uint256(1602786330); } - function initialIssuance() public returns (uint256) { + function initialIssuance() public pure returns (uint256) { return uint256(92592592592592); } - function inflate(uint256 _initial, uint256 _periods) external returns (uint256) { + function inflate(uint256 _initial, uint256 _periods) public pure returns (uint256) { // copy of the implementation from circles contracts v1 // to mirror the same numerical errors as hub v1 has. // https://github.com/CirclesUBI/circles-contracts/blob/master/contracts/Hub.sol#L96-L103 @@ -51,19 +51,19 @@ contract MockHubV1 is IHubV1 { return (_initial * q) / d; } - function inflation() public returns (uint256) { + function inflation() public pure returns (uint256) { return uint256(107); } - function divisor() public returns (uint256) { + function divisor() public pure returns (uint256) { return uint256(100); } - function period() public returns (uint256) { + function period() public pure returns (uint256) { return uint256(31556952); } - function periods() public returns (uint256) { + function periods() public view returns (uint256) { return (block.timestamp - deployedAt()) / period(); } From 7c1ac52d69bf422962570f0d67cf696b955a8ce7 Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Tue, 12 Dec 2023 15:27:15 +0000 Subject: [PATCH 07/11] (migration): convert and migrate circles from V1 to destination graph --- src/graph/IGraph.sol | 8 ++++++-- src/migration/IToken.sol | 4 +++- src/migration/Migration.sol | 38 ++++++++++++++++++++++++++----------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/graph/IGraph.sol b/src/graph/IGraph.sol index 461a61c..588b4d6 100644 --- a/src/graph/IGraph.sol +++ b/src/graph/IGraph.sol @@ -4,10 +4,14 @@ pragma solidity >=0.8.13; import "./ICircleNode.sol"; interface IGraph { + function avatarToCircle(address avatar) external view returns (IAvatarCircleNode); + function checkAllAreTrustedCircleNodes(address group, ICircleNode[] calldata circles, bool includeGroups) external view - returns (bool allTrusted_); + returns (bool allTrusted); + + function migrateCircles(address owner, uint256 amount, IAvatarCircleNode circle) external returns (bool success); - function fetchAllocation(address _avatar) external returns (int128 allocation_, uint256 earliestTimestamp_); + function fetchAllocation(address avatar) external returns (int128 allocation, uint256 earliestTimestamp); } diff --git a/src/migration/IToken.sol b/src/migration/IToken.sol index 6530808..3adb047 100644 --- a/src/migration/IToken.sol +++ b/src/migration/IToken.sol @@ -1,12 +1,14 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.13; +import "../circles/IERC20.sol"; + /** * @title IToken v1 * @author Circles UBI * @notice legacy interface of Hub contract in Circles v1 */ -interface ITokenV1 { +interface ITokenV1 is IERC20 { function owner() external view returns (address); function stopped() external view returns (bool); diff --git a/src/migration/Migration.sol b/src/migration/Migration.sol index 809e84e..1f5199a 100644 --- a/src/migration/Migration.sol +++ b/src/migration/Migration.sol @@ -65,24 +65,40 @@ contract CirclesMigration { * One can only convert personal v1 Circles, if that person has stopped their v1 * circles contract, and has created a v2 demurraged Circles contract by registering in v2. */ - function convertAndMigrateCircles(ITokenV1 _originCircle, uint256 _depositAmount) + function convertAndMigrateCircles(ITokenV1 _originCircle, uint256 _depositAmount, IGraph _destinationGraph) external returns (uint256 mintedAmount_) { - // first check the existance of the origin Circle and whether it is stopped - require(checkOriginCircleStopped(_originCircle), "Origin Circle must have been stopped before conversion."); - - // calculate inflationary correction + // First check the existance of the origin Circle, and associated avatar + address avatar = hubV1.tokenToUser(address(_originCircle)); + require(avatar != address(0), "Origin Circle is unknown to hub v1."); + + // and whether the origin Circle has been stopped. + require(_originCircle.stopped(), "Origin Circle must have been stopped before conversion."); + + // Retrieve the destination Circle where to migrate the tokens to + IAvatarCircleNode destinationCircle = _destinationGraph.avatarToCircle(avatar); + // and check it in fact exists. + require( + address(destinationCircle) != address(0), + "Associated avatar has not been registered in the destination graph." + ); + + // Calculate inflationary correction towards time circles. + uint256 convertedAmount = convertFromV1ToTimeCircles(_depositAmount); + + // transfer the tokens into a permanent lock in this contract + // v1 Circle does not have a burn function exposed, so we can only lock them here + _originCircle.transferFrom(msg.sender, address(this), _depositAmount); + + require( + _destinationGraph.migrateCircles(msg.sender, convertedAmount, destinationCircle), + "Destination graph must succeed at migrating the tokens." + ); } // Public functions - function checkOriginCircleStopped(ITokenV1 _originCircle) public returns (bool stopped_) { - require(hubV1.tokenToUser(address(_originCircle)) != address(0), "Origin Circle is not registered in hub V1."); - - return stopped_ = _originCircle.stopped(); - } - function convertFromV1ToTimeCircles(uint256 _amount) public view returns (uint256 timeCircleAmount_) { uint256 currentPeriod = hubV1.periods(); uint256 nextPeriod = currentPeriod + 1; From 1e3e035af87ed7897aa1cab080e5fa9f7ca94c4e Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Tue, 12 Dec 2023 16:25:51 +0000 Subject: [PATCH 08/11] (migration): migrate full balance --- src/migration/Migration.sol | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/migration/Migration.sol b/src/migration/Migration.sol index 1f5199a..0fa3f2c 100644 --- a/src/migration/Migration.sol +++ b/src/migration/Migration.sol @@ -59,6 +59,16 @@ contract CirclesMigration { // External functions + function convertAndMigrateFullBalanceOfCircles(ITokenV1 _originCircle, IGraph _destinationGraph) + external + returns (uint256 mintedAmount_) + { + uint256 balance = _originCircle.balanceOf(msg.sender); + return mintedAmount_ = convertAndMigrateCircles(_originCircle, balance, _destinationGraph); + } + + // Public functions + /** * @param _depositAmount Deposit amount specifies the amount of inflationary * hub v1 circles the caller wants to convert and migrate to demurraged Circles. @@ -66,7 +76,7 @@ contract CirclesMigration { * circles contract, and has created a v2 demurraged Circles contract by registering in v2. */ function convertAndMigrateCircles(ITokenV1 _originCircle, uint256 _depositAmount, IGraph _destinationGraph) - external + public returns (uint256 mintedAmount_) { // First check the existance of the origin Circle, and associated avatar @@ -95,9 +105,9 @@ contract CirclesMigration { _destinationGraph.migrateCircles(msg.sender, convertedAmount, destinationCircle), "Destination graph must succeed at migrating the tokens." ); - } - // Public functions + return mintedAmount_ = convertedAmount; + } function convertFromV1ToTimeCircles(uint256 _amount) public view returns (uint256 timeCircleAmount_) { uint256 currentPeriod = hubV1.periods(); From 4b570cf31512edcc576bfe4aff3bce9b649957c3 Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Tue, 12 Dec 2023 17:44:41 +0000 Subject: [PATCH 09/11] (migration): adjust graph and time circle for migration --- src/circles/TimeCircle.sol | 8 +++++++- src/graph/Graph.sol | 24 ++++++++++++++++++++++++ src/graph/ICircleNode.sol | 3 +++ test/graph/Graph.t.sol | 3 ++- test/graph/GraphPathTransfer.t.sol | 3 ++- test/graph/MockInternalGraph.sol | 2 +- 6 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/circles/TimeCircle.sol b/src/circles/TimeCircle.sol index 2f1a320..b8409ca 100644 --- a/src/circles/TimeCircle.sol +++ b/src/circles/TimeCircle.sol @@ -77,7 +77,7 @@ contract TimeCircle is MasterCopyNonUpgradable, TemporalDiscount, IAvatarCircleN } modifier notStopped() { - require(!stopped, "Node must not have been stopped."); + require(!stopped, "Circle must not have been stopped."); _; } @@ -132,6 +132,12 @@ contract TimeCircle is MasterCopyNonUpgradable, TemporalDiscount, IAvatarCircleN return _calculateIssuance(_currentTimeSpan()); } + function migrate(address _owner, uint256 _amount) external onlyGraph notStopped returns (bool success_) { + // simply mint the migration amount if the Circle is not stopped + _mint(_owner, _amount); + return true; + } + function burn(uint256 _amount) external { _burn(msg.sender, _amount); } diff --git a/src/graph/Graph.sol b/src/graph/Graph.sol index abe423d..953c617 100644 --- a/src/graph/Graph.sol +++ b/src/graph/Graph.sol @@ -69,6 +69,13 @@ contract Graph is ProxyFactory, IGraph { */ IMintSplitter public immutable mintSplitter; + /** + * @notice Ancestor Circle Migrator contract can call on this graph to migrate + * Circles balance from an account on a Circle contract in Hub v1 + * into the Circle contract of the same associated avatar. + */ + address public immutable ancestorCircleMigrator; + /** * Master copy of the avatar circle node contract to deploy proxy's for * when an avatar signs up. @@ -141,6 +148,11 @@ contract Graph is ProxyFactory, IGraph { // Modifiers + modifier onlyAncestorMigrator() { + require(msg.sender == ancestorCircleMigrator, "Only ancestor circle migrator contract can call this function."); + _; + } + modifier notOnTrustGraph(address _entity) { require( address(avatarToCircle[_entity]) == address(0) && address(organizations[_entity]) == address(0) @@ -171,16 +183,19 @@ contract Graph is ProxyFactory, IGraph { constructor( IMintSplitter _mintSplitter, + address _ancestorCircleMigrator, IAvatarCircleNode _masterCopyAvatarCircleNode, IGroupCircleNode _masterCopyGroupCircleNode ) { require(address(_mintSplitter) != address(0), "Mint Splitter contract must be provided."); + // ancestorCircleMigrator can be zero and left unspecified. It simply disables migration. require( address(_masterCopyAvatarCircleNode) != address(0), "Mastercopy for Avatar Circle Node must not be zero." ); require(address(_masterCopyGroupCircleNode) != address(0), "Mastercopy for Group Circle Node must not be zero."); mintSplitter = _mintSplitter; + ancestorCircleMigrator = _ancestorCircleMigrator; masterCopyAvatarCircleNode = _masterCopyAvatarCircleNode; masterCopyGroupCircleNode = _masterCopyGroupCircleNode; @@ -255,6 +270,15 @@ contract Graph is ProxyFactory, IGraph { emit Trust(msg.sender, _entity, earliestExpiry); } + function migrateCircles(address _owner, uint256 _amount, IAvatarCircleNode _circle) + external + onlyAncestorMigrator + returns (bool success_) + { + require(address(avatarCircleNodesIterable[_circle]) != address(0), "Circle is not registered in this graph."); + return success_ = _circle.migrate(_owner, _amount); + } + function fetchAllocation(address _avatar) external returns (int128 allocation_, uint256 earliestTimestamp_) { require( address(avatarCircleNodesIterable[ICircleNode(msg.sender)]) != address(0), diff --git a/src/graph/ICircleNode.sol b/src/graph/ICircleNode.sol index 8c0d9b6..d326118 100644 --- a/src/graph/ICircleNode.sol +++ b/src/graph/ICircleNode.sol @@ -17,6 +17,9 @@ interface IAvatarCircleNode is ICircleNode { function setup(address avatar) external; function stopped() external view returns (bool stopped); + + // only personal Circles from v1 can be migrated, as group circles were not native in v1 + function migrate(address owner, uint256 amount) external returns (bool success); } interface IGroupCircleNode is ICircleNode { diff --git a/test/graph/Graph.t.sol b/test/graph/Graph.t.sol index 3a01905..b9441eb 100644 --- a/test/graph/Graph.t.sol +++ b/test/graph/Graph.t.sol @@ -39,7 +39,8 @@ contract GraphTest is Test { mintSplitter = new MintSplitter(mockHubV1); - graph = new Graph(mintSplitter, masterCopyTimeCircle, masterCopyGroupCircle); + // create a new graph without ancestor circle migration + graph = new Graph(mintSplitter, address(0), masterCopyTimeCircle, masterCopyGroupCircle); mockInternalGraph = new MockInternalGraph(mintSplitter, masterCopyTimeCircle, masterCopyGroupCircle); } diff --git a/test/graph/GraphPathTransfer.t.sol b/test/graph/GraphPathTransfer.t.sol index ec5215a..6182887 100644 --- a/test/graph/GraphPathTransfer.t.sol +++ b/test/graph/GraphPathTransfer.t.sol @@ -47,7 +47,8 @@ contract GraphPathTransferTest is Test, TimeSetup { mintSplitter = new MintSplitter(mockHubV1); - graph = new Graph(mintSplitter, masterCopyTimeCircle, masterCopyGroupCircle); + // create a new graph without ancestor circle migration + graph = new Graph(mintSplitter, address(0), masterCopyTimeCircle, masterCopyGroupCircle); startTime(); diff --git a/test/graph/MockInternalGraph.sol b/test/graph/MockInternalGraph.sol index 10bae2d..aaeb687 100644 --- a/test/graph/MockInternalGraph.sol +++ b/test/graph/MockInternalGraph.sol @@ -11,7 +11,7 @@ contract MockInternalGraph is Graph { IMintSplitter _mintSplitter, IAvatarCircleNode _masterCopyAvatarCircleNode, IGroupCircleNode _masterCopyGroupCircleNode - ) Graph(_mintSplitter, _masterCopyAvatarCircleNode, _masterCopyGroupCircleNode) {} + ) Graph(_mintSplitter, address(0), _masterCopyAvatarCircleNode, _masterCopyGroupCircleNode) {} // function trust(address _avatar) external override { // notMocked(); From b6343bb27c1e4a44021b7580ffdcfc44032a34b3 Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Tue, 12 Dec 2023 17:54:14 +0000 Subject: [PATCH 10/11] (migration): small review edits --- src/graph/Graph.sol | 2 -- src/migration/Migration.sol | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/graph/Graph.sol b/src/graph/Graph.sol index 953c617..de30685 100644 --- a/src/graph/Graph.sol +++ b/src/graph/Graph.sol @@ -144,8 +144,6 @@ contract Graph is ProxyFactory, IGraph { event Trust(address indexed truster, address indexed trustee, uint256 expiryTime); - event PauseClaim(address indexed claimer, address indexed node); - // Modifiers modifier onlyAncestorMigrator() { diff --git a/src/migration/Migration.sol b/src/migration/Migration.sol index 0fa3f2c..201824b 100644 --- a/src/migration/Migration.sol +++ b/src/migration/Migration.sol @@ -14,8 +14,6 @@ contract CirclesMigration { IHubV1 public immutable hubV1; - // IGraph public immutable graphV2; - uint256 public immutable inflation; uint256 public immutable divisor; uint256 public immutable deployedAt; From 9c47eb30096ff883060aa81d9aaac14b6fe333fa Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Wed, 13 Dec 2023 13:53:36 +0000 Subject: [PATCH 11/11] (migration): update interface for migrating tokens to return the minted amount, which must match the intended amount --- src/circles/TimeCircle.sol | 4 ++-- src/graph/Graph.sol | 4 ++-- src/graph/ICircleNode.sol | 2 +- src/graph/IGraph.sol | 4 +++- src/migration/Migration.sol | 2 +- 5 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/circles/TimeCircle.sol b/src/circles/TimeCircle.sol index b8409ca..7cb299e 100644 --- a/src/circles/TimeCircle.sol +++ b/src/circles/TimeCircle.sol @@ -132,10 +132,10 @@ contract TimeCircle is MasterCopyNonUpgradable, TemporalDiscount, IAvatarCircleN return _calculateIssuance(_currentTimeSpan()); } - function migrate(address _owner, uint256 _amount) external onlyGraph notStopped returns (bool success_) { + function migrate(address _owner, uint256 _amount) external onlyGraph notStopped returns (uint256 migratedAmount_) { // simply mint the migration amount if the Circle is not stopped _mint(_owner, _amount); - return true; + return migratedAmount_ = _amount; } function burn(uint256 _amount) external { diff --git a/src/graph/Graph.sol b/src/graph/Graph.sol index de30685..337ce87 100644 --- a/src/graph/Graph.sol +++ b/src/graph/Graph.sol @@ -271,10 +271,10 @@ contract Graph is ProxyFactory, IGraph { function migrateCircles(address _owner, uint256 _amount, IAvatarCircleNode _circle) external onlyAncestorMigrator - returns (bool success_) + returns (uint256 migratedAmount_) { require(address(avatarCircleNodesIterable[_circle]) != address(0), "Circle is not registered in this graph."); - return success_ = _circle.migrate(_owner, _amount); + return migratedAmount_ = _circle.migrate(_owner, _amount); } function fetchAllocation(address _avatar) external returns (int128 allocation_, uint256 earliestTimestamp_) { diff --git a/src/graph/ICircleNode.sol b/src/graph/ICircleNode.sol index d326118..d2d4f36 100644 --- a/src/graph/ICircleNode.sol +++ b/src/graph/ICircleNode.sol @@ -19,7 +19,7 @@ interface IAvatarCircleNode is ICircleNode { function stopped() external view returns (bool stopped); // only personal Circles from v1 can be migrated, as group circles were not native in v1 - function migrate(address owner, uint256 amount) external returns (bool success); + function migrate(address owner, uint256 amount) external returns (uint256 migratedAmount); } interface IGroupCircleNode is ICircleNode { diff --git a/src/graph/IGraph.sol b/src/graph/IGraph.sol index 588b4d6..32844fe 100644 --- a/src/graph/IGraph.sol +++ b/src/graph/IGraph.sol @@ -11,7 +11,9 @@ interface IGraph { view returns (bool allTrusted); - function migrateCircles(address owner, uint256 amount, IAvatarCircleNode circle) external returns (bool success); + function migrateCircles(address owner, uint256 amount, IAvatarCircleNode circle) + external + returns (uint256 migratedAmount); function fetchAllocation(address avatar) external returns (int128 allocation, uint256 earliestTimestamp); } diff --git a/src/migration/Migration.sol b/src/migration/Migration.sol index 201824b..611168e 100644 --- a/src/migration/Migration.sol +++ b/src/migration/Migration.sol @@ -100,7 +100,7 @@ contract CirclesMigration { _originCircle.transferFrom(msg.sender, address(this), _depositAmount); require( - _destinationGraph.migrateCircles(msg.sender, convertedAmount, destinationCircle), + convertedAmount == _destinationGraph.migrateCircles(msg.sender, convertedAmount, destinationCircle), "Destination graph must succeed at migrating the tokens." );