-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathweb4-min.zig
195 lines (165 loc) · 6.41 KB
/
web4-min.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
const std = @import("std");
// NOTE: In smart contract context don't really have to free memory before execution ends
var allocator = std.heap.wasm_allocator;
// Import host functions provided by NEAR runtime.
// See https://github.com/near/near-sdk-rs/blob/3ca87c95788b724646e0247cfd3feaccec069b97/near-sdk/src/environment/env.rs#L116
// and https://github.com/near/near-sdk-rs/blob/3ca87c95788b724646e0247cfd3feaccec069b97/sys/src/lib.rs
extern fn input(register_id: u64) void;
extern fn signer_account_id(register_id: u64) void;
extern fn current_account_id(register_id: u64) void;
extern fn read_register(register_id: u64, ptr: u64) void;
extern fn register_len(register_id: u64) u64;
extern fn value_return(value_len: u64, value_ptr: u64) void;
extern fn log_utf8(len: u64, ptr: u64) void;
extern fn panic_utf8(len: u64, ptr: u64) void;
extern fn storage_has_key(key_len: u64, key_ptr: u64) u64;
extern fn storage_read(key_len: u64, key_ptr: u64, register_id: u64) u64;
extern fn storage_write(key_len: u64, key_ptr: u64, value_len: u64, value_ptr: u64, register_id: u64) u64;
const SCRATCH_REGISTER = 0xffffffff;
const WEB4_STATIC_URL_KEY = "web4:staticUrl";
const WEB4_OWNER_KEY = "web4:owner";
// Helper wrapper functions for interacting with the host
fn log(str: []const u8) void {
log_utf8(str.len, @intFromPtr(str.ptr));
}
fn panic(str: []const u8) void {
panic_utf8(str.len, @intFromPtr(str.ptr));
}
fn valueReturn(value: []const u8) void {
value_return(value.len, @intFromPtr(value.ptr));
}
fn readRegisterAlloc(register_id: u64) []const u8 {
const len: usize = @truncate(register_len(register_id));
// NOTE: 1 more byte is allocated as allocator.alloc() doesn't allocate 0 bytes
const bytes = allocator.alloc(u8, len + 1) catch {
panic("Failed to allocate memory");
unreachable;
};
read_register(register_id, @intFromPtr(bytes.ptr));
return bytes[0..len];
}
fn readInputAlloc() []const u8 {
input(SCRATCH_REGISTER);
return readRegisterAlloc(SCRATCH_REGISTER);
}
fn readStorageAlloc(key: []const u8) ?[]const u8 {
const res = storage_read(key.len, @intFromPtr(key.ptr), SCRATCH_REGISTER);
return switch (res) {
0 => null,
1 => readRegisterAlloc(SCRATCH_REGISTER),
else => unreachable,
};
}
fn storageWrite(key: []const u8, value: []const u8) bool {
const res = storage_write(key.len, @intFromPtr(key.ptr), value.len, @intFromPtr(value.ptr), SCRATCH_REGISTER);
return switch (res) {
0 => false,
1 => true,
else => unreachable,
};
}
fn joinAlloc(parts: anytype) []const u8 {
var totalSize: usize = 0;
inline for (parts) |part| {
totalSize += part.len;
}
const result = allocator.alloc(u8, totalSize) catch {
panic("Failed to allocate memory");
unreachable;
};
var offset: usize = 0;
inline for (parts) |part| {
@memcpy(result[offset .. offset + part.len], part);
offset += part.len;
}
return result;
}
fn assertSelfOrOwner() void {
current_account_id(SCRATCH_REGISTER);
const contractName = readRegisterAlloc(SCRATCH_REGISTER);
signer_account_id(SCRATCH_REGISTER);
const signerName = readRegisterAlloc(SCRATCH_REGISTER);
const ownerName = readStorageAlloc(WEB4_OWNER_KEY) orelse contractName;
log(joinAlloc(.{ "contractName: ", contractName, ", signerName: ", signerName, ", ownerName: ", ownerName }));
if (!std.mem.eql(u8, contractName, signerName) and !std.mem.eql(u8, ownerName, signerName)) {
panic("Access denied");
unreachable;
}
log("Access allowed");
}
// Default URL, contains some instructions on what to do next
const DEFAULT_STATIC_URL = "ipfs://bafybeidc4lvv4bld66h4rmy2jvgjdrgul5ub5s75vbqrcbjd3jeaqnyd5e";
// Main entry point for web4 contract.
export fn web4_get() void {
// Read method arguments blob
const inputData = readInputAlloc();
// Parse method arguments JSON and extract path
const path = extract_string(inputData, "path") orelse "/";
// Log request path
log(joinAlloc(.{ "path: ", path }));
// Read static URL from storage
const staticUrl = readStorageAlloc(WEB4_STATIC_URL_KEY) orelse DEFAULT_STATIC_URL;
// Construct response object
const responseData = joinAlloc(.{
\\{
\\ "status": 200,
\\ "bodyUrl":
,
"\"",
staticUrl,
path,
"\"",
\\ }
});
// Return method result
valueReturn(responseData);
}
// Parse method arguments JSON
// NOTE: Parsing using std.json.Scanner results in smaller binary than deserializing into object
fn extract_string(inputData: []const u8, keyName: []const u8) ?[]const u8 {
var lastKey: []const u8 = "";
var tokenizer = std.json.Scanner.initCompleteInput(allocator, inputData);
defer tokenizer.deinit();
return while (true) {
_ = switch (tokenizer.next() catch {
panic("Failed to parse JSON");
unreachable;
}) {
.string => |str| {
if (tokenizer.string_is_object_key) {
lastKey = str;
} else if (std.mem.eql(u8, lastKey, keyName)) {
break str;
}
},
.end_of_document => break null,
else => null,
};
};
}
// Update current static content URL in smart contract storage
// NOTE: This is useful for web4-deploy tool
export fn web4_setStaticUrl() void {
assertSelfOrOwner();
// Read method arguments blob
const inputData = readInputAlloc();
// Parse method arguments JSON and extract staticUrl
const staticUrl = extract_string(inputData, "url") orelse DEFAULT_STATIC_URL;
// Log updated URL
log(joinAlloc(.{ "staticUrl: ", staticUrl }));
// Write parsed static URL to storage
_ = storageWrite(WEB4_STATIC_URL_KEY, staticUrl);
}
// Update current owner account ID – if set this account can update contract config
// NOTE: This is useful to deploy contract to subaccount like web4.<account_id>.near and then transfer ownership to <account_id>.near
export fn web4_setOwner() void {
assertSelfOrOwner();
// Read method arguments blob
const inputData = readInputAlloc();
// Parse method arguments JSON and extract owner
const owner = extract_string(inputData, "accountId") orelse "";
// Log updated owner
log(joinAlloc(.{ "owner: ", owner }));
// Write parsed owner to storage
_ = storageWrite(WEB4_OWNER_KEY, owner);
}