Skip to content

Commit 77cf27e

Browse files
authored
Merge pull request #75 from CosmWasm/cw1-subkeys-permissions
cw1-subkeys: Implement permission functionality
2 parents a35cfbe + e88048b commit 77cf27e

File tree

9 files changed

+880
-50
lines changed

9 files changed

+880
-50
lines changed

contracts/cw1-subkeys/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ backtraces = ["cosmwasm-std/backtraces"]
1818
library = []
1919

2020
[dependencies]
21-
cosmwasm-std = { version = "0.10.1", features = ["iterator"] }
21+
cosmwasm-std = { version = "0.10.1", features = ["iterator", "staking"] }
2222
cosmwasm-storage = { version = "0.10.1", features = ["iterator"] }
2323
cw0 = { path = "../../packages/cw0", version = "0.2.0" }
2424
cw1 = { path = "../../packages/cw1", version = "0.2.0" }

contracts/cw1-subkeys/README.md

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ account can try to execute a `CosmosMsg::Bank(BankMsg::Send{})` from this
1616
contract and if they have the required allowances, their allowance will be
1717
reduced and the send message relayed. If they don't have sufficient authorization,
1818
or if they try to proxy any other message type, then the attempt will be rejected.
19+
Admin can give permissions to subkeys to relay specific types of messages
20+
(covers _Delegate, Undelegate, Redelegate, Withdraw_ for now). Subkeys have no permission
21+
on creation, it can be setup with `SetupPermission` message.
1922

2023
### Messages
2124

@@ -34,6 +37,10 @@ enum HandleMsg {
3437
denom: String,
3538
amount: Uint128,
3639
expires: Option<Expiration>,
40+
},
41+
SetupPermissions {
42+
spender: HumanAddr,
43+
permissions: Permissions,
3744
}
3845
}
3946
```
@@ -46,12 +53,22 @@ It also adds one more query type:
4653
enum QueryMsg {
4754
Allowance {
4855
spender: HumanAddr,
49-
}
56+
},
57+
AllAllowances {
58+
start_after: Option<HumanAddr>,
59+
limit: Option<u32>,
60+
},
5061
}
5162

52-
pub struct AllowanceResponse {
53-
pub allowance: Vec<Coin>,
63+
pub struct AllowanceInfo {
64+
pub spender: HumanAddr,
65+
pub balance: Balance,
5466
pub expires: Expiration,
67+
pub permissions: Permissions,
68+
}
69+
70+
pub struct AllAllowancesResponse {
71+
pub allowances: Vec<AllowanceInfo>,
5572
}
5673
```
5774

contracts/cw1-subkeys/helpers.ts

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,35 @@ const useOptions = (options: Options): Network => {
135135

136136
type Expiration = { at_height: { height: number } } | { at_time: { time: number } } | { never: {}}
137137

138-
interface AllowanceResponse {
138+
interface CanSendResponse {
139+
readonly canSend: boolean;
140+
}
141+
142+
interface Permissions {
143+
readonly delegate: boolean
144+
readonly undelegate: boolean
145+
readonly redelegate: boolean
146+
readonly withdraw: boolean
147+
}
148+
149+
interface PermissionsInfo {
150+
readonly spender: string;
151+
readonly permissions: Permissions;
152+
}
153+
154+
interface AllPermissionsResponse {
155+
readonly permissions: readonly PermissionsInfo[];
156+
}
157+
158+
interface AllowanceInfo {
139159
readonly balance: readonly Coin[],
140160
readonly expires: Expiration,
141161
}
142162

163+
interface AllAllowancesResponse {
164+
readonly allowances: readonly AllowanceInfo[];
165+
}
166+
143167
interface AdminListResponse {
144168
readonly admins: readonly string[],
145169
readonly mutable: boolean,
@@ -150,8 +174,7 @@ interface InitMsg {
150174
readonly mutable: boolean,
151175
}
152176

153-
// TODO: define more of these
154-
type CosmosMsg = SendMsg;
177+
type CosmosMsg = SendMsg | DelegateMsg | UndelegateMsg | RedelegateMsg | WithdrawMsg
155178

156179
interface SendMsg {
157180
readonly bank: {
@@ -163,19 +186,62 @@ interface SendMsg {
163186
}
164187
}
165188

189+
interface DelegateMsg {
190+
readonly staking: {
191+
readonly delegate: {
192+
readonly validator: string,
193+
readonly amount: Coin,
194+
}
195+
}
196+
}
197+
198+
interface UndelegateMsg {
199+
readonly staking: {
200+
readonly undelegate: {
201+
readonly validator: string,
202+
readonly amount: Coin,
203+
}
204+
}
205+
}
206+
207+
interface RedelegateMsg {
208+
readonly staking: {
209+
readonly redelegate: {
210+
readonly src_validator: string,
211+
readonly dst_validator: string,
212+
readonly amount: Coin,
213+
}
214+
}
215+
}
216+
217+
interface WithdrawMsg {
218+
readonly staking: {
219+
readonly withdraw: {
220+
readonly validator: string,
221+
readonly recipient?: string,
222+
}
223+
}
224+
}
225+
166226
interface CW1Instance {
167227
readonly contractAddress: string
168228

169229
// queries
170230
admins: () => Promise<AdminListResponse>
171-
allowance: (address?: string) => Promise<AllowanceResponse>
231+
allowance: (address?: string) => Promise<AllowanceInfo>
232+
allAllowances: (startAfter?: string, limit?: number) => Promise<AllAllowancesResponse>
233+
234+
permissions: (address?: string) => Promise<PermissionsInfo>
235+
allPermissions: (startAfter?: string, limit?: number) => Promise<AllPermissionsResponse>
236+
canSend: (sender: string, msg: CosmosMsg) => Promise<CanSendResponse>
172237

173238
// actions
174239
execute: (msgs: readonly CosmosMsg[]) => Promise<string>
175240
freeze: () => Promise<string>
176241
updateAdmins: (admins: readonly string[]) => Promise<string>
177242
increaseAllowance: (recipient: string, amount: Coin, expires?: Expiration) => Promise<string>
178243
decreaseAllowance: (recipient: string, amount: Coin, expires?: Expiration) => Promise<string>
244+
setPermissions: (recipient: string, permissions: Permissions) => Promise<string>
179245
}
180246

181247
interface CW1Contract {
@@ -194,10 +260,26 @@ interface CW1Contract {
194260

195261
const CW1 = (client: SigningCosmWasmClient): CW1Contract => {
196262
const use = (contractAddress: string): CW1Instance => {
197-
const allowance = async (address?: string): Promise<AllowanceResponse> => {
263+
const allowance = async (address?: string): Promise<AllowanceInfo> => {
198264
const spender = address || client.senderAddress;
199-
const result = await client.queryContractSmart(contractAddress, {allowance: { spender }});
200-
return result;
265+
return await client.queryContractSmart(contractAddress, {allowance: {spender}});
266+
};
267+
268+
const allAllowances = async (startAfter?: string, limit?: number): Promise<AllAllowancesResponse> => {
269+
return client.queryContractSmart(contractAddress, {all_allowances: { start_after: startAfter, limit: limit }});
270+
};
271+
272+
const permissions = async (address?: string): Promise<PermissionsInfo> => {
273+
const spender = address || client.senderAddress;
274+
return await client.queryContractSmart(contractAddress, {permissions: {spender}});
275+
};
276+
277+
const allPermissions = async (startAfter?: string, limit?: number): Promise<AllPermissionsResponse> => {
278+
return client.queryContractSmart(contractAddress, {all_permissions: { start_after: startAfter, limit: limit }});
279+
};
280+
281+
const canSend = async (sender: string, msg: CosmosMsg): Promise<CanSendResponse> => {
282+
return client.queryContractSmart(contractAddress, {can_send: { sender: sender, msg: msg }});
201283
};
202284

203285
const admins = async (): Promise<AdminListResponse> => {
@@ -231,16 +313,26 @@ const CW1 = (client: SigningCosmWasmClient): CW1Contract => {
231313
const result = await client.execute(contractAddress, {decrease_allowance: {spender, amount, expires}});
232314
return result.transactionHash;
233315
}
234-
316+
317+
const setPermissions = async (spender: string, permissions: Permissions): Promise<string> => {
318+
const result = await client.execute(contractAddress, {set_permissions: {spender, permissions}});
319+
return result.transactionHash;
320+
}
321+
235322
return {
236323
contractAddress,
237324
admins,
238325
allowance,
326+
allAllowances,
327+
permissions,
328+
allPermissions,
329+
canSend,
239330
execute,
240331
freeze,
241332
updateAdmins,
242333
increaseAllowance,
243334
decreaseAllowance,
335+
setPermissions
244336
};
245337
}
246338

@@ -273,7 +365,7 @@ const CW1 = (client: SigningCosmWasmClient): CW1Contract => {
273365

274366
// Demo:
275367
// const client = await useOptions(coralnetOptions).setup(PASSWORD);
276-
// const { address} = await client.getAccount()
368+
// const { address } = await client.getAccount()
277369
// const factory = CW1(client)
278370
//
279371
// const codeId = await factory.upload();
@@ -314,3 +406,13 @@ const CW1 = (client: SigningCosmWasmClient): CW1Contract => {
314406
// contract.execute([{bank: {send: {from_address: contractAddress, to_address: address, amount: [{denom: "ushell", amount: "440000"}]}}}])
315407
// client.getAccount(contractAddress)
316408
// client.getAccount()
409+
410+
// let permissions: Permissions = { delegate: true, undelegate: true, redelegate: true, withdraw: true }
411+
// contract.setStakingPermissions(randomAddress, permissions)
412+
413+
// test delegating and undelegating from another account
414+
// let dmsg: DelegateMsg = {staking: {delegate: {validator:"coralvaloper1hf50trj7plz2sd8cmcvn7c8ruh3tjhc2uch4gp", amount:{denom:"ureef",amount:"999"}}}}
415+
// contract.execute([dmsg])
416+
//
417+
// let unmsg: UndelegateMsg = {staking: {undelegate: {validator:"coralvaloper1hf50trj7plz2sd8cmcvn7c8ruh3tjhc2uch4gp", amount:{denom:"ureef",amount:"999"}}}}
418+
// contract.execute([unmsg])

contracts/cw1-subkeys/schema/handle_msg.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,29 @@
127127
}
128128
}
129129
}
130+
},
131+
{
132+
"type": "object",
133+
"required": [
134+
"set_permissions"
135+
],
136+
"properties": {
137+
"set_permissions": {
138+
"type": "object",
139+
"required": [
140+
"permissions",
141+
"spender"
142+
],
143+
"properties": {
144+
"permissions": {
145+
"$ref": "#/definitions/Permissions"
146+
},
147+
"spender": {
148+
"$ref": "#/definitions/HumanAddr"
149+
}
150+
}
151+
}
152+
}
130153
}
131154
],
132155
"definitions": {
@@ -282,6 +305,29 @@
282305
"HumanAddr": {
283306
"type": "string"
284307
},
308+
"Permissions": {
309+
"type": "object",
310+
"required": [
311+
"delegate",
312+
"redelegate",
313+
"undelegate",
314+
"withdraw"
315+
],
316+
"properties": {
317+
"delegate": {
318+
"type": "boolean"
319+
},
320+
"redelegate": {
321+
"type": "boolean"
322+
},
323+
"undelegate": {
324+
"type": "boolean"
325+
},
326+
"withdraw": {
327+
"type": "boolean"
328+
}
329+
}
330+
},
285331
"StakingMsg": {
286332
"anyOf": [
287333
{

contracts/cw1-subkeys/schema/query_msg.json

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,26 @@
3434
}
3535
}
3636
},
37+
{
38+
"description": "Get the current permissions for the given subkey (how much it can spend) Returns PermissionsInfo",
39+
"type": "object",
40+
"required": [
41+
"permissions"
42+
],
43+
"properties": {
44+
"permissions": {
45+
"type": "object",
46+
"required": [
47+
"spender"
48+
],
49+
"properties": {
50+
"spender": {
51+
"$ref": "#/definitions/HumanAddr"
52+
}
53+
}
54+
}
55+
}
56+
},
3757
{
3858
"description": "Checks permissions of the caller on this proxy. If CanSend returns true then a call to `Execute` with the same message, before any further state changes, should also succeed.",
3959
"type": "object",
@@ -89,6 +109,38 @@
89109
}
90110
}
91111
}
112+
},
113+
{
114+
"description": "Gets all Permissions for this contract Returns AllPermissionsResponse",
115+
"type": "object",
116+
"required": [
117+
"all_permissions"
118+
],
119+
"properties": {
120+
"all_permissions": {
121+
"type": "object",
122+
"properties": {
123+
"limit": {
124+
"type": [
125+
"integer",
126+
"null"
127+
],
128+
"format": "uint32",
129+
"minimum": 0.0
130+
},
131+
"start_after": {
132+
"anyOf": [
133+
{
134+
"$ref": "#/definitions/HumanAddr"
135+
},
136+
{
137+
"type": "null"
138+
}
139+
]
140+
}
141+
}
142+
}
143+
}
92144
}
93145
],
94146
"definitions": {

0 commit comments

Comments
 (0)