Skip to content

Commit a932404

Browse files
authored
Merge pull request #413 from Concordium/account-abstracted-smart-contract-wallet
Add smart contract wallet
2 parents 03f677d + 4a67ac9 commit a932404

File tree

8 files changed

+2358
-19
lines changed

8 files changed

+2358
-19
lines changed

.github/workflows/linter.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
- examples/cis2-wccd/Cargo.toml
3737
- examples/cis3-nft-sponsored-txs/Cargo.toml
3838
- examples/counter-notify/Cargo.toml
39+
- examples/cis5-smart-contract-wallet/Cargo.toml
3940
- examples/credential-registry/Cargo.toml
4041
- examples/eSealing/Cargo.toml
4142
- examples/factory/Cargo.toml
@@ -714,6 +715,7 @@ jobs:
714715
- examples/cis2-nft/Cargo.toml
715716
- examples/cis3-nft-sponsored-txs/Cargo.toml
716717
- examples/cis2-wccd/Cargo.toml
718+
- examples/cis5-smart-contract-wallet/Cargo.toml
717719
- examples/credential-registry/Cargo.toml
718720
- examples/factory/Cargo.toml
719721
- examples/fib/Cargo.toml
@@ -847,6 +849,7 @@ jobs:
847849
- examples/cis2-nft
848850
- examples/cis3-nft-sponsored-txs
849851
- examples/cis2-wccd
852+
- examples/cis5-smart-contract-wallet
850853
- examples/credential-registry
851854
- examples/factory
852855
- examples/fib
@@ -894,9 +897,9 @@ jobs:
894897
if: ${{ matrix.crates == 'examples/smart-contract-upgrade/contract-version1' }}
895898
run: cargo concordium build --out "examples/smart-contract-upgrade/contract-version2/concordium-out/module.wasm.v1" -- --manifest-path "examples/smart-contract-upgrade/contract-version2/Cargo.toml"
896899

897-
# The 'sponsored-tx-enabled-auction' example needs the wasm module `cis2-multi` for its tests.
900+
# The 'sponsored-tx-enabled-auction' and 'cis5-smart-contract-wallet' examples need the wasm module `cis2-multi` for its tests.
898901
- name: Build cis2-multi module if needed
899-
if: ${{ matrix.crates == 'examples/sponsored-tx-enabled-auction' }}
902+
if: ${{ matrix.crates == 'examples/sponsored-tx-enabled-auction' || matrix.crates == 'examples/cis5-smart-contract-wallet'}}
900903
run: cargo concordium build --out "examples/cis2-multi/concordium-out/module.wasm.v1" -- --manifest-path "examples/cis2-multi/Cargo.toml"
901904

902905
- name: Run cargo concordium test

examples/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ The list of contracts is as follows:
3939
- [proxy](./proxy) A proxy contract that can be put in front of another contract. It works with V0 as well as V1 smart contracts.
4040
- [recorder](./recorder) A contract that records account addresses, and has an entry point to invoke transfers to all those addresses.
4141
- [signature-verifier](./signature-verifier) An example of how to use `crypto_primitives`. The contract verifies an Ed25519 signature.
42+
- [cis5-smart-contract-wallet](./cis5-smart-contract-wallet) An example of how to implement a CIS5 compatible smart contract wallet.
4243
- [nametoken](./nametoken) An example of how to register and manage names as tokens in a smart contract.
4344
- [voting](./voting) An example of how to conduct an election using a smart contract.
4445
- [transfer-policy-check](./transfer-policy-check) A contract that showcases how to use policies.

examples/cis2-multi/src/lib.rs

+23-8
Original file line numberDiff line numberDiff line change
@@ -320,14 +320,16 @@ pub type ContractTokenAmount = TokenAmountU64;
320320

321321
/// The parameter for the contract function `mint` which mints/airdrops a number
322322
/// of tokens to the owner's address.
323-
#[derive(Serialize, SchemaType)]
323+
#[derive(Serialize, SchemaType, Clone)]
324324
pub struct MintParams {
325325
/// Owner of the newly minted tokens.
326-
pub owner: Address,
326+
pub to: Receiver,
327327
/// The metadata_url of the token.
328328
pub metadata_url: MetadataUrl,
329329
/// The token_id to mint/create additional tokens.
330330
pub token_id: ContractTokenId,
331+
/// Additional data that can be sent to the receiving contract.
332+
pub data: AdditionalData,
331333
}
332334

333335
/// The parameter for the contract function `burn` which burns a number
@@ -952,11 +954,13 @@ fn contract_view(_ctx: &ReceiveContext, host: &Host<State>) -> ReceiveResult<Vie
952954
/// function of the state. Logs a `Mint` event.
953955
/// The function assumes that the mint is authorized.
954956
fn mint(
955-
params: MintParams,
957+
params: &MintParams,
956958
host: &mut Host<State>,
957959
logger: &mut impl HasLogger,
958960
) -> ContractResult<()> {
959-
let is_blacklisted = host.state().blacklist.contains(&get_canonical_address(params.owner)?);
961+
let to_address = params.to.address();
962+
963+
let is_blacklisted = host.state().blacklist.contains(&get_canonical_address(to_address)?);
960964

961965
// Check token owner is not blacklisted.
962966
ensure!(!is_blacklisted, CustomContractError::Blacklisted.into());
@@ -970,7 +974,7 @@ fn mint(
970974
let token_metadata = state.mint(
971975
&params.token_id,
972976
&params.metadata_url,
973-
&params.owner,
977+
&to_address,
974978
state.mint_airdrop,
975979
builder,
976980
);
@@ -979,7 +983,7 @@ fn mint(
979983
logger.log(&Cis2Event::Mint(MintEvent {
980984
token_id: params.token_id,
981985
amount: state.mint_airdrop,
982-
owner: params.owner,
986+
owner: to_address,
983987
}))?;
984988

985989
// Metadata URL for the token.
@@ -1030,7 +1034,18 @@ fn contract_mint(
10301034
// ensure!(host.state().has_role(&sender, Roles::MINTER),
10311035
// ContractError::Unauthorized);
10321036

1033-
mint(params, host, logger)?;
1037+
mint(&params, host, logger)?;
1038+
1039+
// If the receiver is a contract: invoke the receive hook function.
1040+
if let Receiver::Contract(address, function) = params.to {
1041+
let parameter = OnReceivingCis2Params {
1042+
token_id: params.token_id,
1043+
amount: host.state.mint_airdrop,
1044+
from: Address::from(address),
1045+
data: params.data,
1046+
};
1047+
host.invoke_contract(&address, &parameter, function.as_entrypoint_name(), Amount::zero())?;
1048+
}
10341049

10351050
Ok(())
10361051
}
@@ -1383,7 +1398,7 @@ fn contract_permit(
13831398
// ContractError::Unauthorized
13841399
// );
13851400

1386-
mint(params, host, logger)?;
1401+
mint(&params, host, logger)?;
13871402
}
13881403
BURN_ENTRYPOINT => {
13891404
// Burn tokens.

examples/cis2-multi/tests/tests.rs

+10-5
Original file line numberDiff line numberDiff line change
@@ -313,12 +313,13 @@ fn test_permit_mint() {
313313

314314
// Create input parameters for the `mint` function.
315315
let payload = MintParams {
316-
owner: ALICE_ADDR,
316+
to: Receiver::from_account(ALICE),
317317
metadata_url: MetadataUrl {
318318
url: "https://some.example/token/2A".to_string(),
319319
hash: None,
320320
},
321321
token_id: TOKEN_1,
322+
data: AdditionalData::empty(),
322323
};
323324

324325
let update =
@@ -893,12 +894,13 @@ fn test_token_balance_of_blacklisted_address_can_not_change() {
893894

894895
// Bob cannot mint tokens to its address.
895896
let mint_params = MintParams {
896-
owner: BOB_ADDR,
897+
to: Receiver::from_account(BOB),
897898
token_id: TOKEN_0,
898899
metadata_url: MetadataUrl {
899900
url: "https://some.example/token/02".to_string(),
900901
hash: None,
901902
},
903+
data: AdditionalData::empty(),
902904
};
903905

904906
let update = chain
@@ -1101,12 +1103,13 @@ fn test_no_execution_of_state_mutative_functions_when_paused() {
11011103

11021104
// Try to mint tokens.
11031105
let params = MintParams {
1104-
owner: ALICE_ADDR,
1106+
to: Receiver::from_account(ALICE),
11051107
metadata_url: MetadataUrl {
11061108
url: "https://some.example/token/02".to_string(),
11071109
hash: None,
11081110
},
11091111
token_id: TOKEN_0,
1112+
data: AdditionalData::empty(),
11101113
};
11111114

11121115
let update_operator = chain
@@ -1306,12 +1309,13 @@ fn initialize_contract_with_alice_tokens(
13061309
let (mut chain, keypairs, contract_address, module_reference) = initialize_chain_and_contract();
13071310

13081311
let mint_params = MintParams {
1309-
owner: ALICE_ADDR,
1312+
to: Receiver::from_account(ALICE),
13101313
token_id: TOKEN_0,
13111314
metadata_url: MetadataUrl {
13121315
url: "https://some.example/token/02".to_string(),
13131316
hash: None,
13141317
},
1318+
data: AdditionalData::empty(),
13151319
};
13161320

13171321
// Mint/airdrop TOKEN_0 to Alice as the owner.
@@ -1325,12 +1329,13 @@ fn initialize_contract_with_alice_tokens(
13251329
.expect("Mint tokens");
13261330

13271331
let mint_params = MintParams {
1328-
owner: ALICE_ADDR,
1332+
to: Receiver::from_account(ALICE),
13291333
token_id: TOKEN_1,
13301334
metadata_url: MetadataUrl {
13311335
url: "https://some.example/token/2A".to_string(),
13321336
hash: None,
13331337
},
1338+
data: AdditionalData::empty(),
13341339
};
13351340

13361341
// Mint/airdrop TOKEN_1 to Alice as the owner.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "smart-contract-wallet"
3+
version = "0.1.0"
4+
authors = ["Concordium <developers@concordium.com>"]
5+
edition = "2021"
6+
license = "MPL-2.0"
7+
8+
[features]
9+
default = ["std", "bump_alloc"]
10+
std = ["concordium-std/std", "concordium-cis2/std"]
11+
bump_alloc = ["concordium-std/bump_alloc"]
12+
13+
[dependencies]
14+
concordium-std = {path = "../../concordium-std", default-features = false}
15+
concordium-cis2 = {path = "../../concordium-cis2", default-features = false, features=[
16+
"u256_amount"]}
17+
18+
[dev-dependencies]
19+
concordium-smart-contract-testing = {path = "../../contract-testing"}
20+
cis2-multi = {path = "../cis2-multi"}
21+
ed25519-dalek = { version = "2.0", features = ["rand_core"] }
22+
rand = "0.8"
23+
24+
[lib]
25+
crate-type=["cdylib", "rlib"]
26+
27+
[profile.release]
28+
codegen-units = 1
29+
opt-level = "s"

0 commit comments

Comments
 (0)