Authorize Function¶
Learn how to implement custom authorization logic in your smart contracts.
Overview¶
Beyond basic authorization checks, you can implement custom authorization logic to handle complex permission scenarios.
Basic Implementation¶
export class MyContract {
authorize(args: authority.authorize_arguments): authority.authorize_result {
const type = args.type;
const call = args.call;
// Custom authorization logic based on call type
if (type == authority.authorization_type.contract_call) {
return this.authorizeContractCall(call);
}
return new authority.authorize_result(false);
}
private authorizeContractCall(call: protocol.operation): authority.authorize_result {
// Implement custom logic
return new authority.authorize_result(true);
}
}
Advanced Authorization¶
Role-Based Authorization¶
export class RoleBasedContract {
roles: Storage.Map<Uint8Array, Storage.List<string>>;
authorize(args: authority.authorize_arguments): authority.authorize_result {
const caller = System.getCaller().caller;
// Check if caller has required role for this operation
if (this.requiresAdminRole(args.call)) {
return new authority.authorize_result(
this.hasRole(caller, "admin")
);
}
// Default authorization logic
return new authority.authorize_result(true);
}
private requiresAdminRole(call: protocol.operation): bool {
// Define which operations require admin role
const adminOperations = ["mint", "burn", "pause"];
return adminOperations.includes(this.getOperationName(call));
}
private hasRole(address: Uint8Array, role: string): bool {
const userRoles = this.roles.get(address);
if (!userRoles) return false;
for (let i = 0; i < userRoles.length(); i++) {
if (userRoles.get(i) == role) return true;
}
return false;
}
}
Time-Based Authorization¶
export class TimeLockContract {
lockUntil: Storage.Obj<u64>;
authorize(args: authority.authorize_arguments): authority.authorize_result {
const currentTime = System.getHeadInfo().head_block_time;
const lockTime = this.lockUntil.get() || 0;
// Deny all operations if contract is time-locked
if (currentTime < lockTime) {
return new authority.authorize_result(false);
}
// Allow operations after lock expires
return new authority.authorize_result(true);
}
}
Multi-Signature Authorization¶
export class MultiSigContract {
required_signatures: Storage.Obj<u32>;
signers: Storage.List<Uint8Array>;
authorize(args: authority.authorize_arguments): authority.authorize_result {
const signatures = this.extractSignatures(args);
const requiredSigs = this.required_signatures.get() || 2;
if (signatures.length < requiredSigs) {
return new authority.authorize_result(false);
}
// Verify all signatures are from valid signers
let validSigs = 0;
for (let i = 0; i < signatures.length; i++) {
if (this.isValidSigner(signatures[i])) {
validSigs++;
}
}
return new authority.authorize_result(validSigs >= requiredSigs);
}
private isValidSigner(signer: Uint8Array): bool {
for (let i = 0; i < this.signers.length(); i++) {
if (Arrays.equal(this.signers.get(i), signer)) {
return true;
}
}
return false;
}
}
Best Practices¶
1. Fail Securely¶
authorize(args: authority.authorize_arguments): authority.authorize_result {
try {
// Authorization logic
return this.performAuthCheck(args);
} catch (error) {
// Default to deny on error
return new authority.authorize_result(false);
}
}
2. Log Authorization Events¶
authorize(args: authority.authorize_arguments): authority.authorize_result {
const caller = System.getCaller().caller;
const authorized = this.checkPermissions(args);
// Log authorization attempt
System.event(
"authorization_check",
this.encodeAuthEvent(caller, args, authorized),
[caller]
);
return new authority.authorize_result(authorized);
}
3. Use Consistent Error Handling¶
authorize(args: authority.authorize_arguments): authority.authorize_result {
// Validate inputs
System.require(args.type != null, "authorization type required");
System.require(args.call != null, "call data required");
// Perform authorization
const result = this.performAuthCheck(args);
return new authority.authorize_result(result);
}
Integration with System¶
Contract Registration¶
export class MyContract {
constructor() {
// Register authorization function with system
System.setContractAuthority(System.getContractId());
}
authorize(args: authority.authorize_arguments): authority.authorize_result {
// Custom authorization logic
return new authority.authorize_result(this.isAuthorized(args));
}
}
Testing Authorization¶
Unit Tests¶
// test/authorization.spec.ts
describe("Authorization", () => {
test("should authorize admin operations", () => {
const contract = new MyContract();
const args = new authority.authorize_arguments();
args.type = authority.authorization_type.contract_call;
const result = contract.authorize(args);
expect(result.authorized).toBe(true);
});
test("should deny unauthorized operations", () => {
const contract = new MyContract();
const args = new authority.authorize_arguments();
args.type = authority.authorization_type.contract_call;
// Set up unauthorized scenario
const result = contract.authorize(args);
expect(result.authorized).toBe(false);
});
});
Common Patterns¶
Owner-Only Authorization¶
authorize(args: authority.authorize_arguments): authority.authorize_result {
const caller = System.getCaller().caller;
const owner = this.owner.get();
return new authority.authorize_result(
Arrays.equal(caller, owner)
);
}
Whitelist Authorization¶
authorize(args: authority.authorize_arguments): authority.authorize_result {
const caller = System.getCaller().caller;
const whitelist = this.whitelist.get() || [];
for (let i = 0; i < whitelist.length; i++) {
if (Arrays.equal(caller, whitelist[i])) {
return new authority.authorize_result(true);
}
}
return new authority.authorize_result(false);
}