Skip to content

Commit 6c93fb5

Browse files
Add support for soroban authorization framework (#1748)
This PR aims to support Soroban authorization framework: - https://github.com/stellar/stellar-protocol/blob/master/core/cap-0046-11.md - https://developers.stellar.org/docs/build/smart-contracts/example-contracts/auth Here are some keynotes to consider: 1- Unlike Solidity, Soroban performs its auth checks using the host. For instance, Soroban doesn't have the construct of `if msg.sender == addr`, however this check is done via a host function `addr.require_auth`. 2- Another function was added `authAsCurrContract`. This is needed for deeper calls. To understand this, take a look at https://github.com/stellar/soroban-examples/tree/main/deep_contract_auth. What I have done here actually is test the auth framework using the above provided example of nested calls. --------- Signed-off-by: salaheldinsoliman <salaheldin_sameh@aucegypt.edu>
1 parent fe96d0d commit 6c93fb5

File tree

18 files changed

+1112
-77
lines changed

18 files changed

+1112
-77
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ forge-fmt = { path = "fmt", optional = true }
7171
# We don't use ethers-core directly, but need the correct version for the
7272
# build to work.
7373
ethers-core = { version = "2.0.10", optional = true }
74-
soroban-sdk = { version = "22.0.0-rc.3.2", features = ["testutils"], optional = true }
74+
soroban-sdk = { version = "22.0.7", features = ["testutils"], optional = true }
7575

7676
[dev-dependencies]
7777
num-derive = "0.4"

integration/soroban/a.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
contract a {
2+
function call_b (address b, address c) public returns (uint64) {
3+
address addr = address(this);
4+
// authorize contract c to be called, with function name "get_num" and "a" as an arg.
5+
// get_num calls a.require_auth()
6+
auth.authAsCurrContract(c, "get_num", addr);
7+
bytes payload = abi.encode("increment", addr, c);
8+
(bool suc, bytes returndata) = b.call(payload);
9+
uint64 result = abi.decode(returndata, (uint64));
10+
return result;
11+
}
12+
}

integration/soroban/a_invalid.sol

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// Same as a.sol, but without a call to auth.authAsCurrContract
2+
contract a_invalid {
3+
function call_b (address b, address c) public returns (uint64) {
4+
address addr = address(this);
5+
// authorize contract c to be called, with function name "get_num" and "a" as an arg.
6+
// get_num calls a.require_auth()
7+
//auth.authAsCurrContract(c, "get_num", addr);
8+
bytes payload = abi.encode("increment", addr, c);
9+
(bool suc, bytes returndata) = b.call(payload);
10+
uint64 result = abi.decode(returndata, (uint64));
11+
return result;
12+
}
13+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as StellarSdk from '@stellar/stellar-sdk';
2+
import { readFileSync } from 'fs';
3+
import { expect } from 'chai';
4+
import path from 'path';
5+
import { fileURLToPath } from 'url';
6+
import { call_contract_function, extractLogEvent } from './test_helpers.js';
7+
import { assert } from 'console';
8+
9+
const __filename = fileURLToPath(import.meta.url);
10+
const dirname = path.dirname(__filename);
11+
const server = new StellarSdk.SorobanRpc.Server("https://soroban-testnet.stellar.org:443");
12+
13+
function readContractAddress(filename) {
14+
return readFileSync(path.join(dirname, '.soroban', 'contract-ids', filename), 'utf8').trim();
15+
}
16+
17+
describe('Auth Framework', () => {
18+
let keypair, a, b, c, a_invalid;
19+
20+
before(async () => {
21+
console.log('Setting up cross contract tests...');
22+
23+
keypair = StellarSdk.Keypair.fromSecret(readFileSync('alice.txt', 'utf8').trim());
24+
a = new StellarSdk.Contract(readContractAddress('a.txt'));
25+
b = new StellarSdk.Contract(readContractAddress('b.txt'));
26+
c = new StellarSdk.Contract(readContractAddress('c.txt'));
27+
a_invalid = new StellarSdk.Contract(readContractAddress('a_invalid.txt'));
28+
});
29+
30+
it('calls a', async () => {
31+
32+
33+
let values = [
34+
b.address().toScVal(),
35+
c.address().toScVal()
36+
];
37+
38+
39+
let res = await call_contract_function("call_b", server, keypair, a, ...values);
40+
41+
42+
expect(res.returnValue().value().toString()).to.equal("22");
43+
44+
});
45+
46+
it ('call falis with invalid `a` contract', async () => {
47+
48+
49+
let values = [
50+
b.address().toScVal(),
51+
c.address().toScVal()
52+
];
53+
54+
let res = await call_contract_function("call_b", server, keypair, a_invalid, ...values);
55+
56+
assert(res.toString().includes("recording authorization only] encountered authorization not tied to the root contract invocation for an address. Use `require_auth()` in the top invocation or enable non-root authorization."));
57+
58+
});
59+
60+
61+
});

integration/soroban/b.sol

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
contract b {
2+
3+
uint64 public instance counter = 20;
4+
5+
function increment(address a, address c) public returns (uint64) {
6+
7+
a.requireAuth();
8+
bytes payload = abi.encode("get_num", a);
9+
(bool suc, bytes returndata) = c.call(payload);
10+
uint64 result = abi.decode(returndata, (uint64));
11+
12+
counter = counter + result;
13+
14+
return counter;
15+
16+
}
17+
}

integration/soroban/c.sol

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
contract c {
2+
function get_num(address a) public returns (uint64) {
3+
a.requireAuth();
4+
return 2;
5+
}
6+
}

src/codegen/encoding/mod.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
mod borsh_encoding;
1212
mod buffer_validator;
1313
pub(super) mod scale_encoding;
14-
mod soroban_encoding;
14+
pub mod soroban_encoding;
1515

1616
use crate::codegen::cfg::{ControlFlowGraph, Instr};
1717
use crate::codegen::encoding::borsh_encoding::BorshEncoding;
@@ -41,7 +41,8 @@ pub(super) fn abi_encode(
4141
packed: bool,
4242
) -> (Expression, Expression) {
4343
if ns.target == Target::Soroban {
44-
return soroban_encode(loc, args, ns, vartab, cfg, packed);
44+
let ret = soroban_encode(loc, args, ns, vartab, cfg, packed);
45+
return (ret.0, ret.1);
4546
}
4647
let mut encoder = create_encoder(ns, packed);
4748
let size = calculate_size_args(&mut encoder, &args, ns, vartab, cfg);
@@ -1431,7 +1432,7 @@ pub(crate) trait AbiEncoding {
14311432
self.get_expr_size(arg_no, &loaded, ns, vartab, cfg)
14321433
}
14331434
Type::StorageRef(_, r) => {
1434-
let var = load_storage(&Codegen, r, expr.clone(), cfg, vartab, None);
1435+
let var = load_storage(&Codegen, r, expr.clone(), cfg, vartab, None, ns);
14351436
let size = self.get_expr_size(arg_no, &var, ns, vartab, cfg);
14361437
self.storage_cache_insert(arg_no, var.clone());
14371438
size

0 commit comments

Comments
 (0)