Skip to content

Commit 4610db7

Browse files
committed
Add withdraw changeset
1 parent a25ed4b commit 4610db7

3 files changed

Lines changed: 248 additions & 0 deletions

File tree

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package changeset
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"math/big"
7+
8+
"github.com/Masterminds/semver/v3"
9+
"github.com/ethereum/go-ethereum/common"
10+
cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm"
11+
cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment"
12+
"github.com/smartcontractkit/chainlink-deployments-framework/operations"
13+
"github.com/smartcontractkit/chainlink-evm/gethwrappers/generated/eth_balance_monitor_wrapper"
14+
"github.com/smartcontractkit/chainlink/deployment/common/proposalutils"
15+
commontypes "github.com/smartcontractkit/chainlink/deployment/common/types"
16+
vaulttypes "github.com/smartcontractkit/chainlink/deployment/vault/changeset/types"
17+
"github.com/smartcontractkit/mcms"
18+
mcmssdk "github.com/smartcontractkit/mcms/sdk"
19+
mcmsevmsdk "github.com/smartcontractkit/mcms/sdk/evm"
20+
mcmstypes "github.com/smartcontractkit/mcms/types"
21+
)
22+
23+
type ethBalMonWithdraw struct{}
24+
25+
var EthBalMonWithdraw cldf.ChangeSetV2[vaulttypes.EthBalMonWithdrawInput] = ethBalMonWithdraw{}
26+
27+
func (w ethBalMonWithdraw) VerifyPreconditions(env cldf.Environment, config vaulttypes.EthBalMonWithdrawInput) error {
28+
return ValidateEthBalMonWithdrawConfig(env.GetContext(), env, config)
29+
}
30+
31+
func (w ethBalMonWithdraw) Apply(e cldf.Environment, config vaulttypes.EthBalMonWithdrawInput) (cldf.ChangesetOutput, error) {
32+
logger := e.Logger
33+
logger.Infow("Generating EthBalMon withdraw proposal", "numChains", len(config.Chains))
34+
35+
evmChains := e.BlockChains.EVMChains()
36+
37+
var primaryChain cldf_evm.Chain
38+
for chainSelector := range config.Chains {
39+
primaryChain = evmChains[chainSelector]
40+
break
41+
}
42+
43+
deps := VaultDeps{
44+
Auth: primaryChain.DeployerKey,
45+
Chain: primaryChain,
46+
Environment: e,
47+
DataStore: e.DataStore,
48+
}
49+
seqInput := EthBalMonWithdrawSeqInput{
50+
Chains: config.Chains,
51+
}
52+
seqReport, err := operations.ExecuteSequence(e.OperationsBundle, EthBalMonWithdrawSequence, deps, seqInput)
53+
if err != nil {
54+
return cldf.ChangesetOutput{}, fmt.Errorf("failed on EthBalMonWithdrawSequence sequence: %w", err)
55+
}
56+
57+
return cldf.ChangesetOutput{
58+
MCMSTimelockProposals: seqReport.Output.MCMSTimelockProposals,
59+
}, nil
60+
}
61+
62+
type EthBalMonWithdrawSeqInput struct {
63+
Chains map[uint64]vaulttypes.EthBalMonWithdrawChainConfig `json:"chains"`
64+
}
65+
66+
type EthBalMonWithdrawSeqOutput struct {
67+
MCMSTimelockProposals []mcms.TimelockProposal `json:"mcms_timelock_proposals"`
68+
}
69+
70+
var EthBalMonWithdrawSequence = operations.NewSequence(
71+
"ethbalmon-withdraw-sequence",
72+
semver.MustParse("1.0.0"),
73+
"Sequence to create operation for EthBalMon withdraw",
74+
func(b operations.Bundle, deps VaultDeps, input EthBalMonWithdrawSeqInput) (EthBalMonWithdrawSeqOutput, error) {
75+
b.Logger.Infow("Starting EthBalMon withdraw sequence",
76+
"chains", len(input.Chains),
77+
)
78+
var batches []mcmstypes.BatchOperation
79+
timelockAddresses := make(map[uint64]string)
80+
mcmAddressByChain := make(map[uint64]string)
81+
inspectorPerChain := make(map[uint64]mcmssdk.Inspector)
82+
for chainSelector, chainConfig := range input.Chains {
83+
opReport, err := operations.ExecuteOperation(b, EthBalMonWithdrawOperation, deps, EthBalMonWithdrawOpInput{
84+
ChainSelector: chainSelector,
85+
Amount: chainConfig.Amount,
86+
Payeer: chainConfig.Payeer,
87+
})
88+
opOutput := opReport.Output
89+
if err != nil {
90+
return EthBalMonWithdrawSeqOutput{}, fmt.Errorf("chain %d: failed to generate withdraw batch: %w", chainSelector, err)
91+
}
92+
batches = append(batches, opOutput.BatchOperation)
93+
timelockAddresses[chainSelector] = opOutput.TimelockAddress
94+
mcmAddressByChain[chainSelector] = opOutput.MCMSAddress
95+
inspectorPerChain[chainSelector] = opOutput.Inspector
96+
}
97+
98+
proposal, err := proposalutils.BuildProposalFromBatchesV2(deps.Environment, timelockAddresses, mcmAddressByChain, inspectorPerChain, batches, "EthBalMon Withdraw", proposalutils.TimelockConfig{
99+
MinDelay: 0,
100+
})
101+
102+
if err != nil {
103+
return EthBalMonWithdrawSeqOutput{}, fmt.Errorf("failed to build timelock proposal: %w", err)
104+
}
105+
b.Logger.Infow("Generated EthBalMon withdraw proposal",
106+
"chains", len(input.Chains), "operations", len(batches))
107+
108+
return EthBalMonWithdrawSeqOutput{
109+
MCMSTimelockProposals: []mcms.TimelockProposal{*proposal},
110+
}, nil
111+
},
112+
)
113+
114+
type EthBalMonWithdrawOpInput struct {
115+
ChainSelector uint64 `json:"chain_selector"`
116+
Amount uint64 `json:"amount"`
117+
Payeer string `json:"payeer"`
118+
}
119+
120+
type EthBalMonWithdrawOpOutput struct {
121+
ChainSelector uint64 `json:"chain_selector"`
122+
BatchOperation mcmstypes.BatchOperation `json:"batch_operation"`
123+
TimelockAddress string `json:"timelock_address"`
124+
MCMSAddress string `json:"mcms_address"`
125+
Inspector *mcmsevmsdk.Inspector `json:"inspector"`
126+
}
127+
128+
var EthBalMonWithdrawOperation = operations.NewOperation(
129+
"ethbalmon-withdraw-operation",
130+
semver.MustParse("1.0.0"),
131+
"",
132+
func(b operations.Bundle, deps VaultDeps, input EthBalMonWithdrawOpInput) (EthBalMonWithdrawOpOutput, error) {
133+
b.Logger.Infow("Starting EthBalMon withdraw operation",
134+
"chainsel", input.ChainSelector,
135+
)
136+
137+
chain, ok := deps.Environment.BlockChains.EVMChains()[input.ChainSelector]
138+
139+
if !ok {
140+
return EthBalMonWithdrawOpOutput{}, fmt.Errorf("chain not found in environment: %d", input.ChainSelector)
141+
}
142+
143+
ethBalMonAddr, err := mustGetContractAddress(
144+
deps.DataStore,
145+
input.ChainSelector,
146+
cldf.ContractType(vaulttypes.ETHBALMON_CONTRACT_TYPE),
147+
)
148+
if err != nil {
149+
return EthBalMonWithdrawOpOutput{},
150+
fmt.Errorf("failed to get EthBalMon address: %w", err)
151+
}
152+
153+
timelockAddr, err := mustGetContractAddress(
154+
deps.DataStore,
155+
input.ChainSelector,
156+
commontypes.RBACTimelock,
157+
)
158+
if err != nil {
159+
return EthBalMonWithdrawOpOutput{},
160+
fmt.Errorf("failed to get timelock address: %w", err)
161+
}
162+
mcmsAddr, err := mustGetContractAddress(
163+
deps.DataStore,
164+
input.ChainSelector,
165+
commontypes.ManyChainMultisig,
166+
)
167+
if err != nil {
168+
return EthBalMonWithdrawOpOutput{},
169+
fmt.Errorf("failed to get MCMS address: %w", err)
170+
}
171+
172+
ethBalMon, err := eth_balance_monitor_wrapper.NewEthBalanceMonitor(common.HexToAddress(ethBalMonAddr), chain.Client)
173+
if err != nil {
174+
return EthBalMonWithdrawOpOutput{},
175+
fmt.Errorf("failed to instantiate EthBalanceMonitor at %s: %w", ethBalMonAddr, err)
176+
}
177+
178+
amountBigInt := big.NewInt(int64(input.Amount))
179+
withdrawTx, err := ethBalMon.Withdraw(cldf.SimTransactOpts(), amountBigInt, common.HexToAddress(input.Payeer))
180+
if err != nil {
181+
return EthBalMonWithdrawOpOutput{}, fmt.Errorf("failed to generate withdraw calldata on chain %d: %w ", input.ChainSelector, err)
182+
}
183+
batch := mcmstypes.BatchOperation{
184+
ChainSelector: mcmstypes.ChainSelector(input.ChainSelector),
185+
Transactions: []mcmstypes.Transaction{
186+
{
187+
OperationMetadata: mcmstypes.OperationMetadata{
188+
ContractType: vaulttypes.ETHBALMON_CONTRACT_TYPE,
189+
Tags: []string{
190+
"withdraw",
191+
},
192+
},
193+
To: ethBalMonAddr,
194+
Data: withdrawTx.Data(),
195+
AdditionalFields: json.RawMessage(`{"value": 0}`),
196+
},
197+
},
198+
}
199+
200+
chainInspector := mcmsevmsdk.NewInspector(chain.Client)
201+
202+
b.Logger.Infow("Generated EthBalMon withdraw batch",
203+
"chainSelector", input.ChainSelector,
204+
"ethBalMon", ethBalMonAddr,
205+
"amount", input.Amount,
206+
"payeer", input.Payeer,
207+
)
208+
209+
return EthBalMonWithdrawOpOutput{
210+
ChainSelector: input.ChainSelector,
211+
BatchOperation: batch,
212+
TimelockAddress: timelockAddr,
213+
MCMSAddress: mcmsAddr,
214+
Inspector: chainInspector,
215+
}, nil
216+
},
217+
)

deployment/vault/changeset/types/types.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ type EthBalMonSetKeeperRegistryAddressInput struct {
103103
Chains map[uint64]SetKeeperRegistryChainConfig `json:"chains"`
104104
}
105105

106+
// setWatchList config
106107
type EthBalMonSetWatchListChainConfig struct {
107108
Addresses []common.Address `json:"addresses"`
108109
MinBalancesWei []big.Int `json:"min_balance_wei"`
@@ -111,3 +112,13 @@ type EthBalMonSetWatchListChainConfig struct {
111112
type EthBalMonSetWatchListInput struct {
112113
Chains map[uint64]EthBalMonSetWatchListChainConfig `json:"chains"`
113114
}
115+
116+
// withdraw config
117+
type EthBalMonWithdrawChainConfig struct {
118+
Amount uint64 `json:"amount"`
119+
Payeer string `json:"payeer"`
120+
}
121+
122+
type EthBalMonWithdrawInput struct {
123+
Chains map[uint64]EthBalMonWithdrawChainConfig `json:"chains"`
124+
}

deployment/vault/changeset/validation.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,3 +269,23 @@ func ValidateSetKeeperRegistryAddressConfig(ctx context.Context, env cldf.Enviro
269269

270270
return nil
271271
}
272+
273+
func ValidateEthBalMonWithdrawConfig(ctx context.Context, env cldf.Environment, cfg types.EthBalMonWithdrawInput) error {
274+
if len(cfg.Chains) == 0 {
275+
return fmt.Errorf("no chains provided")
276+
}
277+
278+
for chainSelector, chainConfig := range cfg.Chains {
279+
if _, ok := env.BlockChains.EVMChains()[chainSelector]; !ok {
280+
return fmt.Errorf("chain not found in environment: %d", chainSelector)
281+
}
282+
if chainConfig.Amount == 0 {
283+
return fmt.Errorf("chain %d: amount to withdraw cannot be 0 (zero)", chainSelector)
284+
}
285+
if !common.IsHexAddress(chainConfig.Payeer) {
286+
return fmt.Errorf("chain %d: invalid payeer address: %s", chainSelector, chainConfig.Payeer)
287+
}
288+
}
289+
290+
return nil
291+
}

0 commit comments

Comments
 (0)