Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .mockery.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ packages:
structname: "{{.Mock}}{{.InterfaceName}}"
interfaces:
AccessControlContract:
AuthorizedCallersContract:
github.com/smartcontractkit/chainlink-deployments-framework/experimental/analyzer:
config:
all: false
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions chain/evm/operations2/contract/write.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"slices"
"strings"
"time"

Expand Down Expand Up @@ -216,6 +217,27 @@ func HasRole[C AccessControlContract](
})
}

type AuthorizedCallersContract interface {
Address() common.Address
GetAllAuthorizedCallers(opts *bind.CallOpts) ([]common.Address, error)
}

// IsAuthorizedCaller returns whether caller is present in the contract's authorized caller set.
func IsAuthorizedCaller[C AuthorizedCallersContract](
contract C,
opts *bind.CallOpts,
caller common.Address,
) (bool, error) {
return RetryContractCall(opts, "authorized caller check", "check authorized caller", contract.Address(), func() (bool, error) {
callers, err := contract.GetAllAuthorizedCallers(opts)
if err != nil {
return false, err
}

return slices.Contains(callers, caller), nil
})
Comment thread
RayXpub marked this conversation as resolved.
}

func AllCallersAllowed[C any, ARGS any](contract C, opts *bind.CallOpts, caller common.Address, args ARGS) (bool, error) {
return true, nil
}
Expand Down
55 changes: 55 additions & 0 deletions chain/evm/operations2/contract/write_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,61 @@ func TestHasRole(t *testing.T) {
}
}

func TestIsAuthorizedCaller(t *testing.T) {
t.Parallel()

account := common.HexToAddress("0x1234")
contractAddress := common.HexToAddress("0xabcd")

tests := []struct {
desc string
opts *bind.CallOpts
setupMock func(*contractmocks.MockAuthorizedCallersContract, *bind.CallOpts)
wantAuthorized bool
}{
{
desc: "returns true when caller is authorized",
setupMock: func(contract *contractmocks.MockAuthorizedCallersContract, opts *bind.CallOpts) {
contract.EXPECT().
Address().
Return(contractAddress).
Once()
contract.EXPECT().
GetAllAuthorizedCallers(opts).
Return([]common.Address{account}, nil).
Once()
},
wantAuthorized: true,
},
{
desc: "returns false when caller is not authorized",
setupMock: func(contract *contractmocks.MockAuthorizedCallersContract, opts *bind.CallOpts) {
contract.EXPECT().
Address().
Return(contractAddress).
Once()
contract.EXPECT().
GetAllAuthorizedCallers(opts).
Return([]common.Address{}, nil).
Once()
},
wantAuthorized: false,
},
}

for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
contract := contractmocks.NewMockAuthorizedCallersContract(t)
test.setupMock(contract, test.opts)

allowed, err := IsAuthorizedCaller(contract, test.opts, account)
require.NoError(t, err)
require.Equal(t, test.wantAuthorized, allowed)
})
}
Comment thread
RayXpub marked this conversation as resolved.
}

func TestRetryContractCall(t *testing.T) {
t.Parallel()

Expand Down
8 changes: 8 additions & 0 deletions tools/operations-gen/generate/templates/evm/operations.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@ func NewWrite{{.Name}}(c gobindings.{{$.ContractType}}Interface) *cld_ops.Operat
IsAllowedCaller: func(c gobindings.{{$.ContractType}}Interface, opts *bind.CallOpts, caller common.Address, args {{.ArgsType}}) (bool, error) {
return contract.HasRole(c, opts, {{.Role}}, caller)
},
{{- else if eq .AccessControl "authorized"}}
IsAllowedCaller: func(c gobindings.{{$.ContractType}}Interface, opts *bind.CallOpts, caller common.Address, args {{.ArgsType}}) (bool, error) {
return contract.IsAuthorizedCaller(c, opts, caller)
},
Comment thread
RayXpub marked this conversation as resolved.
{{- else if eq .AccessControl "private"}}
IsAllowedCaller: func(c gobindings.{{$.ContractType}}Interface, opts *bind.CallOpts, caller common.Address, args {{.ArgsType}}) (bool, error) {
return false, nil
},
Comment thread
RayXpub marked this conversation as resolved.
Comment thread
RayXpub marked this conversation as resolved.
Comment thread
RayXpub marked this conversation as resolved.
{{- else}}
IsAllowedCaller: contract.AllCallersAllowed[gobindings.{{$.ContractType}}Interface, {{.ArgsType}}],
{{- end}}
Expand Down
2 changes: 1 addition & 1 deletion tools/operations-gen/internal/families/evm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type EvmInputConfig struct {
// EvmFunctionConfig selects a contract function and assigns its access control.
type EvmFunctionConfig struct {
Name string `yaml:"name"`
Access string `yaml:"access,omitempty"` // "owner", "role", or "public"
Access string `yaml:"access,omitempty"` // "owner", "role", "authorized", "private" or "public"
// Role is the OpenZeppelin-style role name used when Access is "role".
// Accepted formats:
// - DEFAULT_ADMIN_ROLE → all-zero bytes32
Expand Down
14 changes: 10 additions & 4 deletions tools/operations-gen/internal/families/evm/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import (
)

const (
accessPublic = "public"
accessOwner = "owner"
accessRole = "role"
accessPublic = "public"
accessOwner = "owner"
accessRole = "role"
accessPrivate = "private"
accessAuthorized = "authorized"
)

// ---- Intermediate representation ----
Expand Down Expand Up @@ -233,6 +235,10 @@ func extractFunctions(info *ContractInfo, funcConfigs []EvmFunctionConfig, parse
fi.AccessControl = accessOwner
case accessPublic, "":
fi.AccessControl = accessPublic
case accessPrivate:
fi.AccessControl = accessPrivate
case accessAuthorized:
fi.AccessControl = accessAuthorized
case accessRole:
Comment thread
RayXpub marked this conversation as resolved.
if funcCfg.Role == "" {
return fmt.Errorf("role is required when access is %q for function %s", accessRole, funcCfg.Name)
Expand All @@ -244,7 +250,7 @@ func extractFunctions(info *ContractInfo, funcConfigs []EvmFunctionConfig, parse
fi.AccessControl = accessRole
fi.Role = role
default:
return fmt.Errorf("unknown access control '%s' for function %s (use 'owner', 'public', or 'role')",
return fmt.Errorf("unknown access control '%s' for function %s (use 'owner', 'public', 'authorized', 'private' or 'role')",
funcCfg.Access, funcCfg.Name)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ func TestGenerateRBACTimelock(t *testing.T) {
runGoldenGenerationTest(t, "operations_gen_rbac_timelock_config.yaml", "rbac_timelock.golden.go")
}

func TestGenerateFeeQuoter(t *testing.T) {
t.Parallel()
runGoldenGenerationTest(t, "operations_gen_fee_quoter.yaml", "fee_quoter.golden.go")
}

func runGoldenGenerationTest(t *testing.T, configFileName string, goldenFileName string) {
t.Helper()

Expand Down
63 changes: 63 additions & 0 deletions tools/operations-gen/testdata/evm/fee_quoter.golden.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading