-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore: implement migration script to redistribute funds
Signed-off-by: Jeremy Letang <me@jeremyletang.com>
- Loading branch information
1 parent
4c332ca
commit 0eca822
Showing
4 changed files
with
485 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
// Copyright (C) 2023 Gobalsky Labs Limited | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as | ||
// published by the Free Software Foundation, either version 3 of the | ||
// License, or (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package collateral | ||
|
||
import ( | ||
"context" | ||
|
||
"code.vegaprotocol.io/vega/core/events" | ||
"code.vegaprotocol.io/vega/core/types" | ||
vgcontext "code.vegaprotocol.io/vega/libs/context" | ||
"code.vegaprotocol.io/vega/libs/num" | ||
"code.vegaprotocol.io/vega/logging" | ||
) | ||
|
||
const ( | ||
// The vega asset ID for Tether USD. | ||
TetherUSD = "bf1e88d19db4b3ca0d1d5bdb73718a01686b18cf731ca26adedf3c8b83802bba" | ||
) | ||
|
||
// NOTE: All prices here are expressed in the USDT assets requiring a 6 decimals precision. | ||
|
||
var ( | ||
|
||
// First withdrawal, submitted by key 1d2f37299f436f3b720b8efbbb6beb4aec9145a2c4f398ccb06280f6fa2503e8 | ||
// for a total amount of 92,932.137159 USDT | ||
// https://explorer.vega.xyz/txs/0xAB6D8ECF7333618523E343E3EEB39297DF2D44930A792562F1DA68AC96B2FD96 | ||
withdrawal1USDTAmount = num.MustUintFromString("92932137159", 10) | ||
|
||
// Second withdrawal, submitted by key f6074d2f8924f8c1d73f51bce3faa2e615ef5930ef27a7027576cc8ebaa98d9d | ||
// for a total amount of 13,707.007738 USDT | ||
// https://explorer.vega.xyz/txs/0xDF029EE77141711FE781BB011C1761375364AD4FAB368E6203CD217DA916487D | ||
withdrawal2USDTAmount = num.MustUintFromString("13707007738", 10) | ||
|
||
// Third withdrawal, submitted by key f6074d2f8924f8c1d73f51bce3faa2e615ef5930ef27a7027576cc8ebaa98d9d | ||
// for a total amount of 8,100.000000 USDT | ||
// https://explorer.vega.xyz/txs/0xA1337F3AAB6CAE8403CABC7E5CCDF95E41C3833B6E952905A7EA734F58B34A94 | ||
withdrawal3USDTAmount = num.MustUintFromString("8100000000", 10) | ||
|
||
// The total amount of USDT withdrawn by. | ||
totalWithdrawnUSDTAmount = num.Sum(withdrawal1USDTAmount, withdrawal2USDTAmount, withdrawal3USDTAmount) | ||
|
||
// This slice will contains all recommended amounts to be returns to the parties which endured a lost | ||
// as suggested by the `Flagged withdrawals vs. losses` from the report. | ||
amountsReturned = []struct { | ||
Pubkey string | ||
Amount *num.Uint | ||
}{ | ||
{ | ||
Pubkey: "89c98f0e1039935b5d7f5b8d6d0660790a8e507d0c4234b6cafb7dbf88ad25ca", | ||
Amount: num.MustUintFromString("110027790000", 10), // 110,027.79 USDT | ||
}, | ||
{ | ||
Pubkey: "c8f5d32a8554dbddfa80946fe9ac42d156356f869256aa0a632e5152d45b1316", | ||
Amount: num.MustUintFromString("2872880000", 10), // 2,872.88 USDT | ||
}, | ||
{ | ||
Pubkey: "426f40b09ea2388c22e7c409b6e979747597316939ed6b422c5b935069ad4814", | ||
Amount: num.MustUintFromString("128530000", 10), // 128.53 USDT | ||
}, | ||
{ | ||
Pubkey: "519d2af4058af1bed4e05859afa6a15cb1791166df8f0fe3f70a783a13232440", | ||
Amount: num.MustUintFromString("114110000", 10), // 114.11 USDT | ||
}, | ||
{ | ||
Pubkey: "1d150c717d349e901cc26e511f776c323c1b8a8dbb0e7717183f2a1e9f3482d7", | ||
Amount: num.MustUintFromString("80840000", 10), // 80.84 USDT | ||
}, | ||
{ | ||
Pubkey: "36e73d371b25f0d97ce7813d688c42e61792bda80c00c9cf6d8bf9424a539bf5", | ||
Amount: num.MustUintFromString("71080000", 10), // 71.08 USDT | ||
}, | ||
{ | ||
Pubkey: "0a9b24a83cb661e68a2069a413cc2603f0f4804b165621806fa8a014fb0ed4b5", | ||
Amount: num.MustUintFromString("50640000", 10), // 50.64 USDT | ||
}, | ||
// this last entry is the key from the perpetrator, and the funds to be returned to them | ||
{ | ||
Pubkey: "1d2f37299f436f3b720b8efbbb6beb4aec9145a2c4f398ccb06280f6fa2503e8", | ||
Amount: num.MustUintFromString("1393274897", 10), // 1,393.274897 USDT | ||
}, | ||
} | ||
) | ||
|
||
// OnStateLoaded is a hook call by the snapshot engine, | ||
// it will be called once the whole state of the core have been | ||
// restored from the snaphshot, then execute the migration to | ||
// credit back the different accounts. | ||
func (e *Engine) OnStateLoaded(ctx context.Context) error { | ||
if vgcontext.InProgressUpgradeFrom(ctx, "v0.74.3") { | ||
e.migrate744 = true | ||
} | ||
return nil | ||
} | ||
|
||
// ExecuteMigration744 This function will iterate other the map and execute Deposits for all | ||
// the keys back to their general account. | ||
func ExecuteMigration744( | ||
ctx context.Context, | ||
broker Broker, | ||
log *logging.Logger, | ||
c *Engine, | ||
) { | ||
log.Info("starting migration 74.4") | ||
|
||
// keep track of the ledger movement so they can be sent to the datanode | ||
ledgerMovements := []*types.LedgerMovement{} | ||
|
||
// we take a copy of the total amount which was attempted to be withdrawn | ||
// and will decrement it with all the deposits executed to ensure that nothing | ||
// is left out but also not too much funds are being credited | ||
totalAmount := totalWithdrawnUSDTAmount.Clone() | ||
for _, entry := range amountsReturned { | ||
// to start with, ensure that the amount left to distributed is > to the amount that needs to be credited to that pubkey | ||
if totalAmount.LT(entry.Amount) { | ||
log.Panic("total amount left to distribute is too low", logging.BigUint("amount", totalAmount), logging.BigUint("toCredit", entry.Amount)) | ||
} | ||
|
||
// before the deposit we get the amount from the party general account balance | ||
// these should never fail has the party should already have a general account | ||
accountBefore, err := c.GetPartyGeneralAccount(entry.Pubkey, TetherUSD) | ||
if err != nil { | ||
log.Panic("unexpected error loading account before deposit", logging.Error(err)) | ||
} | ||
|
||
log.Info("account state before deposit", logging.String("pubkey", entry.Pubkey), logging.BigUint("balance", accountBefore.Balance)) | ||
|
||
// execute the deposit | ||
ledgerMovement, err := c.Deposit(ctx, entry.Pubkey, TetherUSD, entry.Amount) | ||
if err != nil { | ||
log.Panic("unexpected error during deposit", logging.Error(err)) | ||
} | ||
|
||
// loading balance after the deposit to ensure the right amount was deposited | ||
accountAfter, err := c.GetPartyGeneralAccount(entry.Pubkey, TetherUSD) | ||
if err != nil { | ||
log.Panic("unexpected error loading account afer deposit", logging.Error(err)) | ||
} | ||
|
||
log.Info("account state after deposit", logging.String("pubkey", entry.Pubkey), logging.BigUint("balance", accountAfter.Balance)) | ||
|
||
// here we just assert the right mount has been deposited | ||
expectedBalance := num.Sum(accountBefore.Balance, entry.Amount) | ||
if accountAfter.Balance.NEQ(expectedBalance) { | ||
log.Panic("invalid balance after deposit", logging.BigUint("expected", expectedBalance), logging.BigUint("got", accountAfter.Balance)) | ||
} | ||
|
||
// decrement the totalAmount left to distribute | ||
totalAmount.Sub(totalAmount, entry.Amount) | ||
|
||
// add the ledgerMovement to the slice | ||
ledgerMovements = append(ledgerMovements, ledgerMovement) | ||
} | ||
|
||
// ensure the total amount left is zero and all balances have been updated properly | ||
if !totalAmount.IsZero() { | ||
log.Panic("funds have not been fully distributed", logging.BigUint("remaining", totalAmount)) | ||
} | ||
|
||
// send the events to the datanode | ||
broker.Send(events.NewLedgerMovements(ctx, ledgerMovements)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
// Copyright (C) 2023 Gobalsky Labs Limited | ||
// | ||
// This program is free software: you can redistribute it and/or modify | ||
// it under the terms of the GNU Affero General Public License as | ||
// published by the Free Software Foundation, either version 3 of the | ||
// License, or (at your option) any later version. | ||
// | ||
// This program is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
// GNU Affero General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Affero General Public License | ||
// along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
package collateral_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
bmocks "code.vegaprotocol.io/vega/core/broker/mocks" | ||
"code.vegaprotocol.io/vega/core/collateral" | ||
"code.vegaprotocol.io/vega/core/collateral/mocks" | ||
"code.vegaprotocol.io/vega/core/events" | ||
"code.vegaprotocol.io/vega/core/types" | ||
"code.vegaprotocol.io/vega/libs/config/encoding" | ||
"code.vegaprotocol.io/vega/libs/num" | ||
"code.vegaprotocol.io/vega/logging" | ||
|
||
"github.com/golang/mock/gomock" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
var ( | ||
pubkeys = []string{ | ||
"89c98f0e1039935b5d7f5b8d6d0660790a8e507d0c4234b6cafb7dbf88ad25ca", | ||
"c8f5d32a8554dbddfa80946fe9ac42d156356f869256aa0a632e5152d45b1316", | ||
"426f40b09ea2388c22e7c409b6e979747597316939ed6b422c5b935069ad4814", | ||
"519d2af4058af1bed4e05859afa6a15cb1791166df8f0fe3f70a783a13232440", | ||
"1d150c717d349e901cc26e511f776c323c1b8a8dbb0e7717183f2a1e9f3482d7", | ||
"36e73d371b25f0d97ce7813d688c42e61792bda80c00c9cf6d8bf9424a539bf5", | ||
"0a9b24a83cb661e68a2069a413cc2603f0f4804b165621806fa8a014fb0ed4b5", | ||
// perpetrator key: | ||
"1d2f37299f436f3b720b8efbbb6beb4aec9145a2c4f398ccb06280f6fa2503e8", | ||
} | ||
amountsReturned = map[string]*num.Uint{ | ||
"89c98f0e1039935b5d7f5b8d6d0660790a8e507d0c4234b6cafb7dbf88ad25ca": num.MustUintFromString("110027790000", 10), // 110,027.79 USDT | ||
"c8f5d32a8554dbddfa80946fe9ac42d156356f869256aa0a632e5152d45b1316": num.MustUintFromString("2872880000", 10), // 2,872.88 USDT | ||
"426f40b09ea2388c22e7c409b6e979747597316939ed6b422c5b935069ad4814": num.MustUintFromString("128530000", 10), // 128.53 USDT | ||
"519d2af4058af1bed4e05859afa6a15cb1791166df8f0fe3f70a783a13232440": num.MustUintFromString("114110000", 10), // 114.11 USDT | ||
"1d150c717d349e901cc26e511f776c323c1b8a8dbb0e7717183f2a1e9f3482d7": num.MustUintFromString("80840000", 10), // 80.84 USDT | ||
"36e73d371b25f0d97ce7813d688c42e61792bda80c00c9cf6d8bf9424a539bf5": num.MustUintFromString("71080000", 10), // 71.08 USDT | ||
"0a9b24a83cb661e68a2069a413cc2603f0f4804b165621806fa8a014fb0ed4b5": num.MustUintFromString("50640000", 10), // 50.64 USDT | ||
// this last entry is the key from the perpetrator, and the funds to be returned to them | ||
"1d2f37299f436f3b720b8efbbb6beb4aec9145a2c4f398ccb06280f6fa2503e8": num.MustUintFromString("1393274897", 10), // 1,393.274897 USDT | ||
} | ||
) | ||
|
||
func TestPatchV0744(t *testing.T) { | ||
c := setupEngine(t) | ||
|
||
// parameters for the migration function | ||
ctx := context.Background() | ||
log := logging.NewTestLogger() | ||
|
||
// create the account for the parties first | ||
// this is only for the test context, on mainnet | ||
// those parties will exist | ||
c.broker.EXPECT().Send(gomock.Any()).Times(16) | ||
for _, pubkey := range pubkeys { | ||
_, err := c.CreatePartyGeneralAccount(ctx, pubkey, collateral.TetherUSD) | ||
assert.NoError(t, err) | ||
} | ||
|
||
c.broker.EXPECT().Send(gomock.Any()).Times(8) | ||
c.broker.EXPECT().Send(gomock.Any()).Times(1).Do(func(e events.Event) { | ||
lme, ok := e.(*events.LedgerMovements) | ||
assert.True(t, ok) | ||
movements := lme.LedgerMovements() | ||
assert.Equal(t, len(pubkeys), len(movements)) | ||
for _, lm := range movements { | ||
entries := lm.Entries | ||
assert.Equal(t, 1, len(entries)) | ||
entry := entries[0] | ||
// get the pubkey | ||
key := entry.ToAccount.GetOwner() | ||
expAmt, ok := amountsReturned[key] | ||
assert.True(t, ok) | ||
assert.Equal(t, expAmt.String(), entry.Amount) | ||
// ensure the balance - in this test, because we have just created the accounts, matches the uint amount | ||
assert.Equal(t, expAmt.String(), entry.ToAccountBalance) | ||
// just for sanity, make sure we're still using the correct asset | ||
assert.Equal(t, collateral.TetherUSD, entry.ToAccount.AssetId) | ||
} | ||
}) | ||
// anything failing in the Migration function would trigger a Panic | ||
// this test just assert that no panics happe | ||
assert.NotPanics(t, | ||
func() { | ||
collateral.ExecuteMigration744(ctx, c.broker, log, c.Engine) | ||
}, | ||
"migration panic'd at runtime!", | ||
) | ||
} | ||
|
||
func setupEngine(t *testing.T) *testEngine { | ||
t.Helper() | ||
|
||
// intantiate our collateral engine | ||
ctrl := gomock.NewController(t) | ||
timeSvc := mocks.NewMockTimeService(ctrl) | ||
timeSvc.EXPECT().GetTimeNow().AnyTimes() | ||
|
||
broker := bmocks.NewMockBroker(ctrl) | ||
conf := collateral.NewDefaultConfig() | ||
conf.Level = encoding.LogLevel{Level: logging.DebugLevel} | ||
broker.EXPECT().Send(gomock.Any()).Times(7) | ||
|
||
eng := collateral.New(logging.NewTestLogger(), conf, timeSvc, broker) | ||
|
||
// enable the assert for the tests | ||
usdtAsset := types.Asset{ | ||
ID: collateral.TetherUSD, | ||
Details: &types.AssetDetails{ | ||
Symbol: "USDT", | ||
Name: "Tether USD", | ||
Decimals: 6, | ||
Quantum: num.DecimalOne(), | ||
Source: &types.AssetDetailsBuiltinAsset{ | ||
BuiltinAsset: &types.BuiltinAsset{ | ||
MaxFaucetAmountMint: num.UintZero(), | ||
}, | ||
}, | ||
}, | ||
} | ||
|
||
err := eng.EnableAsset(context.Background(), usdtAsset) | ||
assert.NoError(t, err) | ||
|
||
return &testEngine{ | ||
Engine: eng, | ||
ctrl: ctrl, | ||
timeSvc: timeSvc, | ||
broker: broker, | ||
} | ||
} |
Oops, something went wrong.