|
1 | 1 | package evmgrantrole |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "errors" |
| 5 | + "fmt" |
| 6 | + "math/big" |
| 7 | + |
4 | 8 | "github.com/Masterminds/semver/v3" |
5 | 9 | "github.com/ethereum/go-ethereum/accounts/abi/bind" |
6 | 10 | "github.com/ethereum/go-ethereum/common" |
7 | 11 | "github.com/ethereum/go-ethereum/core/types" |
8 | | - |
| 12 | + cldf_evm "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm" |
| 13 | + opscontract "github.com/smartcontractkit/chainlink-deployments-framework/chain/evm/operations2/contract" |
| 14 | + cldf "github.com/smartcontractkit/chainlink-deployments-framework/deployment" |
9 | 15 | mcmscontracts "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/contracts/mcms" |
10 | | - "github.com/smartcontractkit/mcms/sdk/evm/bindings" |
| 16 | + cldfproposalutils "github.com/smartcontractkit/chainlink-deployments-framework/engine/cld/mcms/proposalutils" |
| 17 | + "github.com/smartcontractkit/chainlink-deployments-framework/operations" |
| 18 | + mcmssdk "github.com/smartcontractkit/mcms/sdk" |
| 19 | + mcmsevm "github.com/smartcontractkit/mcms/sdk/evm" |
| 20 | + mcmsbindings "github.com/smartcontractkit/mcms/sdk/evm/bindings" |
11 | 21 |
|
12 | | - evmops "github.com/smartcontractkit/cld-changesets/legacy/mcms/oputils" |
| 22 | + "github.com/smartcontractkit/cld-changesets/mcms/evm/internal/gasboost" |
13 | 23 | ) |
14 | 24 |
|
15 | | -// OpGrantRoleInput is the input to OpGrantRole. |
16 | | -type OpGrantRoleInput struct { |
17 | | - Account common.Address `json:"account"` |
18 | | - RoleID [32]byte `json:"roleID"` |
| 25 | +type GrantRoleTarget struct { |
| 26 | + Timelock common.Address `json:"timelock"` |
| 27 | + Role mcmssdk.TimelockRole `json:"role"` |
| 28 | + Address common.Address `json:"address"` |
| 29 | +} |
| 30 | + |
| 31 | +type OpEVMGrantRoleInput struct { |
| 32 | + Target GrantRoleTarget `json:"target"` |
| 33 | + NoSend bool `json:"noSend"` |
| 34 | + GasPrice uint64 `json:"gasPrice"` |
| 35 | + GasLimit uint64 `json:"gasLimit"` |
19 | 36 | } |
20 | 37 |
|
21 | | -// OpGrantRole grants the given role to the given account on the EVM Timelock contract. |
22 | | -// TODO: refactor to use mcms lib |
23 | | -var OpGrantRole = evmops.NewEVMCallOperation( |
| 38 | +func (in OpEVMGrantRoleInput) GasBoostValues() (gasLimit, gasPrice uint64) { |
| 39 | + return in.GasLimit, in.GasPrice |
| 40 | +} |
| 41 | + |
| 42 | +func (in OpEVMGrantRoleInput) WithGasBoost(gasLimit, gasPrice uint64) OpEVMGrantRoleInput { |
| 43 | + in.GasLimit = gasLimit |
| 44 | + in.GasPrice = gasPrice |
| 45 | + |
| 46 | + return in |
| 47 | +} |
| 48 | + |
| 49 | +var OpEVMGrantRole = operations.NewOperation( |
24 | 50 | "evm-timelock-grant-role", |
25 | 51 | semver.MustParse("1.0.0"), |
26 | | - "Grants the specified role to the given account on the EVM Timelock contract", |
27 | | - bindings.RBACTimelockABI, |
28 | | - mcmscontracts.RBACTimelock, |
29 | | - bindings.NewRBACTimelock, |
30 | | - func(timelock *bindings.RBACTimelock, opts *bind.TransactOpts, input OpGrantRoleInput) (*types.Transaction, error) { |
31 | | - return timelock.GrantRole(opts, input.RoleID, input.Account) |
| 52 | + "Grants an RBACTimelock role to one EVM address via the MCMS SDK timelock configurer", |
| 53 | + func(b operations.Bundle, deps cldf_evm.Chain, in OpEVMGrantRoleInput) (opscontract.WriteOutput, error) { |
| 54 | + if !in.NoSend && deps.DeployerKey == nil { |
| 55 | + return opscontract.WriteOutput{}, fmt.Errorf("missing deployer key for chain %d", deps.Selector) |
| 56 | + } |
| 57 | + |
| 58 | + var opts *bind.TransactOpts |
| 59 | + if in.NoSend { |
| 60 | + opts = cldf.SimTransactOpts() |
| 61 | + } else { |
| 62 | + opts = gasboost.CloneTransactOptsWithGas(deps.DeployerKey, in.GasLimit, in.GasPrice) |
| 63 | + } |
| 64 | + if opts == nil { |
| 65 | + return opscontract.WriteOutput{}, fmt.Errorf("failed to build transact opts for chain %d", deps.Selector) |
| 66 | + } |
| 67 | + opts.Context = b.GetContext() |
| 68 | + |
| 69 | + configurer := mcmsevm.NewTimelockConfigurer(deps.Client, opts) |
| 70 | + res, err := configurer.GrantRole(b.GetContext(), in.Target.Timelock.Hex(), in.Target.Role, in.Target.Address.Hex()) |
| 71 | + if err != nil { |
| 72 | + return opscontract.WriteOutput{}, fmt.Errorf("failed to grant role %s to %s on %s: %w", |
| 73 | + in.Target.Role.String(), in.Target.Address.Hex(), in.Target.Timelock.Hex(), err) |
| 74 | + } |
| 75 | + |
| 76 | + tx, err := rawTransaction(res.RawData) |
| 77 | + if err != nil { |
| 78 | + return opscontract.WriteOutput{}, err |
| 79 | + } |
| 80 | + |
| 81 | + out := writeOutputFromGrant(deps.Selector, in.Target.Timelock, tx) |
| 82 | + if in.NoSend { |
| 83 | + return out, nil |
| 84 | + } |
| 85 | + |
| 86 | + if _, err = cldf.ConfirmIfNoErrorWithABI(deps, tx, mcmsbindings.RBACTimelockABI, nil); err != nil { |
| 87 | + return opscontract.WriteOutput{}, fmt.Errorf("failed to confirm grant role tx against %s: %w", in.Target.Timelock.Hex(), err) |
| 88 | + } |
| 89 | + b.Logger.Infow("GrantRole tx confirmed", "txHash", tx.Hash().Hex(), "timelock", in.Target.Timelock.Hex()) |
| 90 | + |
| 91 | + out.ExecInfo = &opscontract.ExecInfo{Hash: tx.Hash().Hex()} |
| 92 | + |
| 93 | + return out, nil |
32 | 94 | }, |
33 | 95 | ) |
| 96 | + |
| 97 | +func writeOutputFromGrant(chainSelector uint64, timelock common.Address, tx *types.Transaction) opscontract.WriteOutput { |
| 98 | + return opscontract.WriteOutput{ |
| 99 | + ChainSelector: chainSelector, |
| 100 | + Tx: mcmsevm.NewTransaction( |
| 101 | + timelock, |
| 102 | + tx.Data(), |
| 103 | + big.NewInt(0), |
| 104 | + string(mcmscontracts.RBACTimelock), |
| 105 | + []string{}, |
| 106 | + ), |
| 107 | + } |
| 108 | +} |
| 109 | + |
| 110 | +func rawTransaction(raw any) (*types.Transaction, error) { |
| 111 | + switch tx := raw.(type) { |
| 112 | + case *types.Transaction: |
| 113 | + return tx, nil |
| 114 | + default: |
| 115 | + return nil, fmt.Errorf("unexpected raw data type %T from GrantRole", raw) |
| 116 | + } |
| 117 | +} |
| 118 | + |
| 119 | +func retryGrantRoleWithGasBoost(cfg *cldfproposalutils.GasBoostConfig) operations.ExecuteOption[OpEVMGrantRoleInput, cldf_evm.Chain] { |
| 120 | + if cfg == nil { |
| 121 | + return operations.WithRetry[OpEVMGrantRoleInput, cldf_evm.Chain]() |
| 122 | + } |
| 123 | + |
| 124 | + return gasboost.RetryWithGasBoost[OpEVMGrantRoleInput](cfg) |
| 125 | +} |
| 126 | + |
| 127 | +func validateGrantRoleTarget(target GrantRoleTarget) error { |
| 128 | + if target.Timelock == (common.Address{}) { |
| 129 | + return errors.New("timelock address must not be zero") |
| 130 | + } |
| 131 | + if !target.Role.Valid() { |
| 132 | + return errors.New("role is unsupported") |
| 133 | + } |
| 134 | + if target.Address == (common.Address{}) { |
| 135 | + return errors.New("address must not be zero") |
| 136 | + } |
| 137 | + |
| 138 | + return nil |
| 139 | +} |
0 commit comments