diff --git a/governance_contract/test_snapshots/tests/set_fee_config_blocked_when_paused.1.json b/governance_contract/test_snapshots/tests/set_fee_config_blocked_when_paused.1.json
index 12e5284..8efd122 100644
--- a/governance_contract/test_snapshots/tests/set_fee_config_blocked_when_paused.1.json
+++ b/governance_contract/test_snapshots/tests/set_fee_config_blocked_when_paused.1.json
@@ -1,6 +1,6 @@
{
"generators": {
- "address": 2,
+ "address": 4,
"nonce": 0
},
"auth": [
@@ -10,30 +10,54 @@
{
"function": {
"contract_fn": {
- "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
"function_name": "init",
"args": [
{
- "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ {
+ "u32": 2
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
}
]
}
},
"sub_invocations": []
}
- ]
- ],
- [
+ ],
[
- "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
{
"function": {
"contract_fn": {
- "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
- "function_name": "pause",
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "init",
"args": [
{
- "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ {
+ "u32": 2
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
}
]
}
@@ -48,29 +72,40 @@
{
"function": {
"contract_fn": {
- "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
- "function_name": "set_fee_config",
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "pause",
"args": [
{
- "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
- },
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ],
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "pause",
+ "args": [
{
- "map": [
+ "vec": [
{
- "key": {
- "symbol": "network_fee_bps"
- },
- "val": {
- "u32": 50
- }
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
},
{
- "key": {
- "symbol": "platform_fee_bps"
- },
- "val": {
- "u32": 100
- }
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
}
]
}
@@ -80,7 +115,8 @@
"sub_invocations": []
}
]
- ]
+ ],
+ []
],
"ledger": {
"protocol_version": 21,
@@ -161,10 +197,10 @@
[
{
"contract_data": {
- "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
"key": {
"ledger_key_nonce": {
- "nonce": 5541220902715666415
+ "nonce": 4837995959683129791
}
},
"durability": "temporary"
@@ -176,10 +212,10 @@
"data": {
"contract_data": {
"ext": "v0",
- "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
"key": {
"ledger_key_nonce": {
- "nonce": 5541220902715666415
+ "nonce": 4837995959683129791
}
},
"durability": "temporary",
@@ -196,13 +232,11 @@
"contract_data": {
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
"key": {
- "vec": [
- {
- "symbol": "FeeConfig"
- }
- ]
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
},
- "durability": "persistent"
+ "durability": "temporary"
}
},
[
@@ -213,44 +247,23 @@
"ext": "v0",
"contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
"key": {
- "vec": [
- {
- "symbol": "FeeConfig"
- }
- ]
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
},
- "durability": "persistent",
- "val": {
- "map": [
- {
- "key": {
- "symbol": "network_fee_bps"
- },
- "val": {
- "u32": 50
- }
- },
- {
- "key": {
- "symbol": "platform_fee_bps"
- },
- "val": {
- "u32": 100
- }
- }
- ]
- }
+ "durability": "temporary",
+ "val": "void"
}
},
"ext": "v0"
},
- 518400
+ 6311999
]
],
[
{
"contract_data": {
- "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
"key": "ledger_key_contract_instance",
"durability": "persistent"
}
@@ -261,7 +274,7 @@
"data": {
"contract_data": {
"ext": "v0",
- "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
"key": "ledger_key_contract_instance",
"durability": "persistent",
"val": {
@@ -279,7 +292,14 @@
]
},
"val": {
- "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
}
},
{
@@ -293,6 +313,30 @@
"val": {
"bool": true
}
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "RecoveryAddress"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Threshold"
+ }
+ ]
+ },
+ "val": {
+ "u32": 2
+ }
}
]
}
@@ -301,7 +345,7 @@
},
"ext": "v0"
},
- 100000
+ 518400
]
],
[
@@ -322,7 +366,7 @@
},
"ext": "v0"
},
- 100000
+ 518400
]
]
]
@@ -340,12 +384,49 @@
"symbol": "fn_call"
},
{
- "bytes": "0000000000000000000000000000000000000000000000000000000000000002"
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000004"
},
{
"symbol": "init"
}
],
+ "data": {
+ "vec": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ {
+ "u32": 2
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "contract",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "initialized"
+ }
+ ],
"data": {
"address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
}
@@ -357,7 +438,7 @@
{
"event": {
"ext": "v0",
- "contract_id": "0000000000000000000000000000000000000000000000000000000000000002",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
"type_": "diagnostic",
"body": {
"v0": {
@@ -387,14 +468,21 @@
"symbol": "fn_call"
},
{
- "bytes": "0000000000000000000000000000000000000000000000000000000000000002"
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000004"
},
{
"symbol": "pause"
}
],
"data": {
- "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
}
}
}
@@ -404,13 +492,13 @@
{
"event": {
"ext": "v0",
- "contract_id": "0000000000000000000000000000000000000000000000000000000000000002",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
"type_": "contract",
"body": {
"v0": {
"topics": [
{
- "symbol": "pause"
+ "symbol": "paused"
}
],
"data": {
@@ -431,7 +519,7 @@
{
"event": {
"ext": "v0",
- "contract_id": "0000000000000000000000000000000000000000000000000000000000000002",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
"type_": "diagnostic",
"body": {
"v0": {
@@ -461,7 +549,7 @@
"symbol": "fn_call"
},
{
- "bytes": "0000000000000000000000000000000000000000000000000000000000000002"
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000004"
},
{
"symbol": "set_fee_config"
@@ -470,7 +558,14 @@
"data": {
"vec": [
{
- "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
},
{
"map": [
@@ -479,7 +574,7 @@
"symbol": "network_fee_bps"
},
"val": {
- "u32": 50
+ "u32": 35
}
},
{
@@ -487,7 +582,7 @@
"symbol": "platform_fee_bps"
},
"val": {
- "u32": 100
+ "u32": 120
}
}
]
@@ -502,37 +597,141 @@
{
"event": {
"ext": "v0",
- "contract_id": "0000000000000000000000000000000000000000000000000000000000000002",
- "type_": "contract",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
"body": {
"v0": {
"topics": [
{
- "symbol": "fee_config_updated"
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 5
+ }
}
],
"data": {
"vec": [
{
- "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ "string": "failing with contract error"
},
{
- "map": [
+ "u32": 5
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": true
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 5
+ }
+ }
+ ],
+ "data": {
+ "string": "escalating error to panic"
+ }
+ }
+ }
+ },
+ "failed_call": true
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 5
+ }
+ }
+ ],
+ "data": {
+ "string": "caught error from function"
+ }
+ }
+ }
+ },
+ "failed_call": true
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 5
+ }
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "string": "contract call failed"
+ },
+ {
+ "symbol": "set_fee_config"
+ },
+ {
+ "vec": [
{
- "key": {
- "symbol": "network_fee_bps"
- },
- "val": {
- "u32": 50
- }
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
},
{
- "key": {
- "symbol": "platform_fee_bps"
- },
- "val": {
- "u32": 100
- }
+ "map": [
+ {
+ "key": {
+ "symbol": "network_fee_bps"
+ },
+ "val": {
+ "u32": 35
+ }
+ },
+ {
+ "key": {
+ "symbol": "platform_fee_bps"
+ },
+ "val": {
+ "u32": 120
+ }
+ }
+ ]
}
]
}
@@ -546,19 +745,23 @@
{
"event": {
"ext": "v0",
- "contract_id": "0000000000000000000000000000000000000000000000000000000000000002",
+ "contract_id": null,
"type_": "diagnostic",
"body": {
"v0": {
"topics": [
{
- "symbol": "fn_return"
+ "symbol": "error"
},
{
- "symbol": "set_fee_config"
+ "error": {
+ "contract": 5
+ }
}
],
- "data": "void"
+ "data": {
+ "string": "escalating error to panic"
+ }
}
}
},
diff --git a/governance_contract/test_snapshots/tests/set_fee_config_checks_paused_before_auth_and_validation.1.json b/governance_contract/test_snapshots/tests/set_fee_config_checks_paused_before_auth_and_validation.1.json
new file mode 100644
index 0000000..17525fe
--- /dev/null
+++ b/governance_contract/test_snapshots/tests/set_fee_config_checks_paused_before_auth_and_validation.1.json
@@ -0,0 +1,765 @@
+{
+ "generators": {
+ "address": 5,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "init",
+ "args": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ {
+ "u32": 2
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ],
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "init",
+ "args": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ {
+ "u32": 2
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "pause",
+ "args": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ],
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "pause",
+ "args": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 21,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 6311999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 1033654523790656264
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 6311999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 4837995959683129791
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 6311999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 6311999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Paused"
+ }
+ ]
+ },
+ "val": {
+ "bool": true
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "RecoveryAddress"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Threshold"
+ }
+ ]
+ },
+ "val": {
+ "u32": 2
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 518400
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 518400
+ ]
+ ]
+ ]
+ },
+ "events": [
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "fn_call"
+ },
+ {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000004"
+ },
+ {
+ "symbol": "init"
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ {
+ "u32": 2
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "contract",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "initialized"
+ }
+ ],
+ "data": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "fn_return"
+ },
+ {
+ "symbol": "init"
+ }
+ ],
+ "data": "void"
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "fn_call"
+ },
+ {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000004"
+ },
+ {
+ "symbol": "pause"
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "contract",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "paused"
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "bool": true
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "fn_return"
+ },
+ {
+ "symbol": "pause"
+ }
+ ],
+ "data": "void"
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "fn_call"
+ },
+ {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000004"
+ },
+ {
+ "symbol": "set_fee_config"
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
+ }
+ ]
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "network_fee_bps"
+ },
+ "val": {
+ "u32": 4
+ }
+ },
+ {
+ "key": {
+ "symbol": "platform_fee_bps"
+ },
+ "val": {
+ "u32": 5001
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 5
+ }
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "string": "failing with contract error"
+ },
+ {
+ "u32": 5
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": true
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 5
+ }
+ }
+ ],
+ "data": {
+ "string": "escalating error to panic"
+ }
+ }
+ }
+ },
+ "failed_call": true
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 5
+ }
+ }
+ ],
+ "data": {
+ "string": "caught error from function"
+ }
+ }
+ }
+ },
+ "failed_call": true
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 5
+ }
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "string": "contract call failed"
+ },
+ {
+ "symbol": "set_fee_config"
+ },
+ {
+ "vec": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM"
+ }
+ ]
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "network_fee_bps"
+ },
+ "val": {
+ "u32": 4
+ }
+ },
+ {
+ "key": {
+ "symbol": "platform_fee_bps"
+ },
+ "val": {
+ "u32": 5001
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 5
+ }
+ }
+ ],
+ "data": {
+ "string": "escalating error to panic"
+ }
+ }
+ }
+ },
+ "failed_call": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/governance_contract/test_snapshots/tests/set_fee_config_rejects_signatures_below_threshold_when_not_paused.1.json b/governance_contract/test_snapshots/tests/set_fee_config_rejects_signatures_below_threshold_when_not_paused.1.json
new file mode 100644
index 0000000..666cc30
--- /dev/null
+++ b/governance_contract/test_snapshots/tests/set_fee_config_rejects_signatures_below_threshold_when_not_paused.1.json
@@ -0,0 +1,556 @@
+{
+ "generators": {
+ "address": 4,
+ "nonce": 0
+ },
+ "auth": [
+ [
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "init",
+ "args": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ {
+ "u32": 2
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ],
+ [
+ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ {
+ "function": {
+ "contract_fn": {
+ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "function_name": "init",
+ "args": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ {
+ "u32": 2
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ }
+ },
+ "sub_invocations": []
+ }
+ ]
+ ],
+ []
+ ],
+ "ledger": {
+ "protocol_version": 21,
+ "sequence_number": 0,
+ "timestamp": 0,
+ "network_id": "0000000000000000000000000000000000000000000000000000000000000000",
+ "base_reserve": 0,
+ "min_persistent_entry_ttl": 4096,
+ "min_temp_entry_ttl": 16,
+ "max_entry_ttl": 6312000,
+ "ledger_entries": [
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 801925984706572462
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 6311999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4",
+ "key": {
+ "ledger_key_nonce": {
+ "nonce": 5541220902715666415
+ }
+ },
+ "durability": "temporary",
+ "val": "void"
+ }
+ },
+ "ext": "v0"
+ },
+ 6311999
+ ]
+ ],
+ [
+ {
+ "contract_data": {
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_data": {
+ "ext": "v0",
+ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4",
+ "key": "ledger_key_contract_instance",
+ "durability": "persistent",
+ "val": {
+ "contract_instance": {
+ "executable": {
+ "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ },
+ "storage": [
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Admin"
+ }
+ ]
+ },
+ "val": {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "RecoveryAddress"
+ }
+ ]
+ },
+ "val": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ },
+ {
+ "key": {
+ "vec": [
+ {
+ "symbol": "Threshold"
+ }
+ ]
+ },
+ "val": {
+ "u32": 2
+ }
+ }
+ ]
+ }
+ }
+ }
+ },
+ "ext": "v0"
+ },
+ 4095
+ ]
+ ],
+ [
+ {
+ "contract_code": {
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+ }
+ },
+ [
+ {
+ "last_modified_ledger_seq": 0,
+ "data": {
+ "contract_code": {
+ "ext": "v0",
+ "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "code": ""
+ }
+ },
+ "ext": "v0"
+ },
+ 4095
+ ]
+ ]
+ ]
+ },
+ "events": [
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "fn_call"
+ },
+ {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000004"
+ },
+ {
+ "symbol": "init"
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4"
+ }
+ ]
+ },
+ {
+ "u32": 2
+ },
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M"
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "contract",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "initialized"
+ }
+ ],
+ "data": {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "fn_return"
+ },
+ {
+ "symbol": "init"
+ }
+ ],
+ "data": "void"
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "fn_call"
+ },
+ {
+ "bytes": "0000000000000000000000000000000000000000000000000000000000000004"
+ },
+ {
+ "symbol": "set_fee_config"
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "network_fee_bps"
+ },
+ "val": {
+ "u32": 35
+ }
+ },
+ {
+ "key": {
+ "symbol": "platform_fee_bps"
+ },
+ "val": {
+ "u32": 120
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 3
+ }
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "string": "failing with contract error"
+ },
+ {
+ "u32": 3
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": true
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 3
+ }
+ }
+ ],
+ "data": {
+ "string": "escalating error to panic"
+ }
+ }
+ }
+ },
+ "failed_call": true
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": "0000000000000000000000000000000000000000000000000000000000000004",
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 3
+ }
+ }
+ ],
+ "data": {
+ "string": "caught error from function"
+ }
+ }
+ }
+ },
+ "failed_call": true
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 3
+ }
+ }
+ ],
+ "data": {
+ "vec": [
+ {
+ "string": "contract call failed"
+ },
+ {
+ "symbol": "set_fee_config"
+ },
+ {
+ "vec": [
+ {
+ "vec": [
+ {
+ "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM"
+ }
+ ]
+ },
+ {
+ "map": [
+ {
+ "key": {
+ "symbol": "network_fee_bps"
+ },
+ "val": {
+ "u32": 35
+ }
+ },
+ {
+ "key": {
+ "symbol": "platform_fee_bps"
+ },
+ "val": {
+ "u32": 120
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "failed_call": false
+ },
+ {
+ "event": {
+ "ext": "v0",
+ "contract_id": null,
+ "type_": "diagnostic",
+ "body": {
+ "v0": {
+ "topics": [
+ {
+ "symbol": "error"
+ },
+ {
+ "error": {
+ "contract": 3
+ }
+ }
+ ],
+ "data": {
+ "string": "escalating error to panic"
+ }
+ }
+ }
+ },
+ "failed_call": false
+ }
+ ]
+}
\ No newline at end of file
diff --git a/settlement_contract/src/admin.rs b/settlement_contract/src/admin.rs
index db13cba..6405ea0 100644
--- a/settlement_contract/src/admin.rs
+++ b/settlement_contract/src/admin.rs
@@ -9,9 +9,9 @@ use bettapay_common::{
use crate::errors::SettlementError;
use crate::storage::{
- assert_not_paused, is_merchant_registered_internal, read_admin, read_admins,
- read_governance, read_pending_recovery, read_recovery_address, read_rule_or_default,
- read_threshold, validate_admins_and_threshold, validate_governance, validate_nonzero_address,
+ assert_not_paused, is_merchant_registered_internal, read_admin, read_admins, read_governance,
+ read_pending_recovery, read_recovery_address, read_rule_or_default, read_threshold,
+ validate_admins_and_threshold, validate_governance, validate_nonzero_address,
verify_admin_auth,
};
use crate::types::{DataKey, Operation, SettlementRule};
@@ -23,517 +23,515 @@ use crate::{
#[contractimpl]
impl SettlementContract {
- pub fn supports_interface(_env: Env, version: u32) -> bool {
- version == 1
+ pub fn supports_interface(_env: Env, version: u32) -> bool {
+ version == 1
+ }
+
+ /// Initialize the contract with the given admin address.
+ ///
+ /// # Panics
+ ///
+ /// * [`AlreadyInitialized`](SettlementError::AlreadyInitialized) — if the contract has already been initialized.
+ pub fn init(
+ env: Env,
+ admins: Vec
,
+ threshold: u32,
+ governance: Address,
+ recovery_address: Address,
+ ) {
+ if env.storage().instance().has(&DataKey::Admin) {
+ panic_with_error!(&env, SettlementError::AlreadyInitialized);
}
-
- /// Initialize the contract with the given admin address.
- ///
- /// # Panics
- ///
- /// * [`AlreadyInitialized`](SettlementError::AlreadyInitialized) — if the contract has already been initialized.
- pub fn init(
- env: Env,
- admins: Vec,
- threshold: u32,
- governance: Address,
- recovery_address: Address,
- ) {
- if env.storage().instance().has(&DataKey::Admin) {
- panic_with_error!(&env, SettlementError::AlreadyInitialized);
- }
- validate_admins_and_threshold(&env, &admins, threshold);
- validate_governance(&env, &governance);
- validate_nonzero_address(
- &env,
- &recovery_address,
- SettlementError::InvalidRecoveryAddress,
- SettlementError::InvalidRecoveryAddress,
- );
- for i in 0..threshold {
- admins.get(i).unwrap().require_auth();
- }
- env.storage().instance().set(&DataKey::Admin, &admins);
- env.storage()
- .instance()
- .set(&DataKey::Threshold, &threshold);
- env.storage()
- .instance()
- .set(&DataKey::Governance, &governance);
- env.storage()
- .instance()
- .set(&CommonDataKey::RecoveryAddress, &recovery_address);
+ validate_admins_and_threshold(&env, &admins, threshold);
+ validate_governance(&env, &governance);
+ validate_nonzero_address(
+ &env,
+ &recovery_address,
+ SettlementError::InvalidRecoveryAddress,
+ SettlementError::InvalidRecoveryAddress,
+ );
+ for i in 0..threshold {
+ admins.get(i).unwrap().require_auth();
}
-
- pub fn is_initialized(env: Env) -> bool {
- env.storage().instance().has(&DataKey::Admin)
+ env.storage().instance().set(&DataKey::Admin, &admins);
+ env.storage()
+ .instance()
+ .set(&DataKey::Threshold, &threshold);
+ env.storage()
+ .instance()
+ .set(&DataKey::Governance, &governance);
+ env.storage()
+ .instance()
+ .set(&CommonDataKey::RecoveryAddress, &recovery_address);
+ }
+
+ pub fn is_initialized(env: Env) -> bool {
+ env.storage().instance().has(&DataKey::Admin)
+ }
+
+ pub fn get_admin(env: Env) -> Vec {
+ read_admins(&env)
+ }
+
+ pub fn get_threshold(env: Env) -> u32 {
+ read_threshold(&env)
+ }
+
+ pub fn get_governance(env: Env) -> Address {
+ read_governance(&env)
+ }
+
+ pub fn get_recovery_address(env: Env) -> Address {
+ read_recovery_address(&env)
+ }
+
+ pub fn update_governance(env: Env, signers: Vec, new_governance: Address) {
+ verify_admin_auth(&env, &signers, read_threshold(&env));
+ assert_not_paused(&env);
+ validate_governance(&env, &new_governance);
+ let admin = signers.get(0).unwrap();
+ env.storage()
+ .instance()
+ .set(&DataKey::Governance, &new_governance);
+ env.events().publish(
+ (Symbol::new(&env, "governance_updated"),),
+ (admin, new_governance),
+ );
+ }
+
+ pub fn initiate_recovery(env: Env, new_admin: Address) {
+ let recovery_address = read_recovery_address(&env);
+ recovery_address.require_auth();
+ validate_nonzero_address(
+ &env,
+ &new_admin,
+ SettlementError::InvalidAdmin,
+ SettlementError::InvalidAdmin,
+ );
+
+ let pending = PendingRecovery {
+ new_admin: new_admin.clone(),
+ execute_after: env.ledger().timestamp() + RECOVERY_DELAY_SECONDS,
+ };
+ env.storage()
+ .instance()
+ .set(&CommonDataKey::PendingRecovery, &pending);
+ events::emit_recovery_initiated(&env, &recovery_address, &new_admin, pending.execute_after);
+ }
+
+ pub fn cancel_recovery(env: Env, signers: Vec) {
+ verify_admin_auth(&env, &signers, read_threshold(&env));
+ let admin = signers.get(0).unwrap();
+ if !env
+ .storage()
+ .instance()
+ .has(&CommonDataKey::PendingRecovery)
+ {
+ panic_with_error!(&env, SettlementError::RecoveryNotPending);
}
-
- pub fn get_admin(env: Env) -> Vec {
- read_admins(&env)
+ env.storage()
+ .instance()
+ .remove(&CommonDataKey::PendingRecovery);
+ events::emit_recovery_cancelled(&env, &admin);
+ }
+
+ pub fn execute_recovery(env: Env) {
+ let pending = read_pending_recovery(&env);
+ if env.ledger().timestamp() < pending.execute_after {
+ panic_with_error!(&env, SettlementError::RecoveryDelayActive);
}
- pub fn get_threshold(env: Env) -> u32 {
- read_threshold(&env)
+ let old_admin = read_admin(&env);
+ let new_admins = soroban_sdk::vec![&env, pending.new_admin.clone()];
+ env.storage().instance().set(&DataKey::Admin, &new_admins);
+ env.storage().instance().set(&DataKey::Threshold, &1u32);
+ env.storage()
+ .instance()
+ .remove(&CommonDataKey::PendingRecovery);
+ events::emit_recovery_executed(
+ &env,
+ &AdminTransferred {
+ old_admin,
+ new_admin: pending.new_admin.clone(),
+ },
+ );
+ }
+
+ pub fn transfer_admin(
+ env: Env,
+ signers: Vec,
+ new_admins: Vec,
+ new_threshold: u32,
+ ) {
+ verify_admin_auth(&env, &signers, read_threshold(&env));
+ validate_admins_and_threshold(&env, &new_admins, new_threshold);
+
+ let old_admin = read_admin(&env);
+ env.storage().instance().set(&DataKey::Admin, &new_admins);
+ env.storage()
+ .instance()
+ .set(&DataKey::Threshold, &new_threshold);
+ let primary_new_admin = new_admins.get(0).unwrap();
+ events::emit_admin_transferred(
+ &env,
+ &AdminTransferred {
+ old_admin,
+ new_admin: primary_new_admin,
+ },
+ );
+ }
+
+ pub fn change_threshold(env: Env, signers: Vec, new_threshold: u32) {
+ let current_threshold = read_threshold(&env);
+ verify_admin_auth(&env, &signers, current_threshold + 1);
+
+ let admins = read_admins(&env);
+ if new_threshold == 0 || new_threshold > admins.len() {
+ panic_with_error!(&env, SettlementError::InvalidThreshold);
}
- pub fn get_governance(env: Env) -> Address {
- read_governance(&env)
+ env.storage()
+ .instance()
+ .set(&DataKey::Threshold, &new_threshold);
+ env.events().publish(
+ (Symbol::new(&env, "threshold_changed"),),
+ (current_threshold, new_threshold),
+ );
+ }
+
+ pub fn upgrade(env: Env, signers: Vec, new_wasm_hash: BytesN<32>) {
+ verify_admin_auth(&env, &signers, read_threshold(&env));
+ let admin = signers.get(0).unwrap();
+
+ let event_wasm_hash = new_wasm_hash.clone();
+ env.events().publish(
+ (Symbol::new(&env, "contract_upgraded"), event_wasm_hash),
+ admin,
+ );
+
+ env.deployer().update_current_contract_wasm(new_wasm_hash);
+ }
+
+ pub fn pause(env: Env, signers: Vec) {
+ verify_admin_auth(&env, &signers, read_threshold(&env));
+ let admin = signers.get(0).unwrap();
+ storage::set_paused(&env, true);
+ events::emit_paused(&env, &admin);
+ }
+
+ pub fn unpause(env: Env, signers: Vec) {
+ verify_admin_auth(&env, &signers, read_threshold(&env));
+ let admin = signers.get(0).unwrap();
+ storage::set_paused(&env, false);
+ events::emit_unpaused(&env, &admin);
+ }
+
+ pub fn is_paused(env: Env) -> bool {
+ storage::is_paused(&env)
+ }
+
+ /// Schedules an administrative operation to be executed after a timelock.
+ pub fn schedule(env: Env, caller: Address, operation: Operation, execute_in: u64) {
+ let admin = read_admin(&env);
+ if caller != admin {
+ panic_with_error!(&env, SettlementError::Unauthorized);
}
+ caller.require_auth();
- pub fn get_recovery_address(env: Env) -> Address {
- read_recovery_address(&env)
+ if execute_in < DEFAULT_TIMELOCK_DELAY_SECONDS {
+ panic_with_error!(&env, SettlementError::ExecutionNotReady);
}
- pub fn update_governance(env: Env, signers: Vec, new_governance: Address) {
- verify_admin_auth(&env, &signers, read_threshold(&env));
- assert_not_paused(&env);
- validate_governance(&env, &new_governance);
- let admin = signers.get(0).unwrap();
- env.storage()
- .instance()
- .set(&DataKey::Governance, &new_governance);
- env.events().publish(
- (Symbol::new(&env, "governance_updated"),),
- (admin, new_governance),
- );
- }
-
- pub fn initiate_recovery(env: Env, new_admin: Address) {
- let recovery_address = read_recovery_address(&env);
- recovery_address.require_auth();
- validate_nonzero_address(
- &env,
- &new_admin,
- SettlementError::InvalidAdmin,
- SettlementError::InvalidAdmin,
- );
+ let op_hash: BytesN<32> = env.crypto().sha256(&operation.clone().to_xdr(&env)).into();
+ let key = DataKey::ScheduledOperation(op_hash.clone());
- let pending = PendingRecovery {
- new_admin: new_admin.clone(),
- execute_after: env.ledger().timestamp() + RECOVERY_DELAY_SECONDS,
- };
- env.storage()
- .instance()
- .set(&CommonDataKey::PendingRecovery, &pending);
- events::emit_recovery_initiated(&env, &recovery_address, &new_admin, pending.execute_after);
+ if env.storage().persistent().has(&key) {
+ panic_with_error!(&env, SettlementError::OperationAlreadyScheduled);
}
- pub fn cancel_recovery(env: Env, signers: Vec) {
- verify_admin_auth(&env, &signers, read_threshold(&env));
- let admin = signers.get(0).unwrap();
- if !env
- .storage()
- .instance()
- .has(&CommonDataKey::PendingRecovery)
- {
- panic_with_error!(&env, SettlementError::RecoveryNotPending);
- }
- env.storage()
- .instance()
- .remove(&CommonDataKey::PendingRecovery);
- events::emit_recovery_cancelled(&env, &admin);
+ let execute_at = env.ledger().timestamp() + execute_in;
+ env.storage().persistent().set(&key, &execute_at);
+ env.storage()
+ .persistent()
+ .extend_ttl(&key, 17280 * 14, 17280 * 30);
+
+ env.events().publish(
+ (Symbol::new(&env, "op_scheduled"), op_hash),
+ (caller, execute_at),
+ );
+ }
+
+ /// Executes a previously scheduled administrative operation.
+ pub fn execute(env: Env, operation: Operation) {
+ let op_hash: BytesN<32> = env.crypto().sha256(&operation.clone().to_xdr(&env)).into();
+ let key = DataKey::ScheduledOperation(op_hash.clone());
+
+ let execute_at: u64 = env
+ .storage()
+ .persistent()
+ .get(&key)
+ .unwrap_or_else(|| panic_with_error!(&env, SettlementError::OperationNotScheduled));
+
+ if env.ledger().timestamp() < execute_at {
+ panic_with_error!(&env, SettlementError::ExecutionNotReady);
}
- pub fn execute_recovery(env: Env) {
- let pending = read_pending_recovery(&env);
- if env.ledger().timestamp() < pending.execute_after {
- panic_with_error!(&env, SettlementError::RecoveryDelayActive);
- }
+ env.storage().persistent().remove(&key);
- let old_admin = read_admin(&env);
- let new_admins = soroban_sdk::vec![&env, pending.new_admin.clone()];
- env.storage().instance().set(&DataKey::Admin, &new_admins);
- env.storage().instance().set(&DataKey::Threshold, &1u32);
- env.storage()
- .instance()
- .remove(&CommonDataKey::PendingRecovery);
- events::emit_recovery_executed(
- &env,
- &AdminTransferred {
- old_admin,
- new_admin: pending.new_admin.clone(),
- },
- );
+ match operation {
+ Operation::UpdateGovernance(new_gov) => Self::_update_governance(&env, new_gov),
+ Operation::CancelRecovery => {
+ let admin = read_admin(&env);
+ admin.require_auth();
+ Self::_cancel_recovery(&env)
+ }
+ Operation::TransferAdmin(new_admin) => Self::_transfer_admin(&env, new_admin),
+ Operation::Upgrade(wasm_hash) => Self::_upgrade(&env, wasm_hash),
+ Operation::RegisterMerchant(merchant) => Self::_register_merchant(&env, merchant),
+ Operation::UnregisterMerchant(merchant) => Self::_unregister_merchant(&env, merchant),
+ Operation::SetSettlementRule(merchant, rule) => {
+ Self::_set_settlement_rule(&env, merchant, rule)
+ }
+ Operation::ClearSettlementRule(merchant) => {
+ Self::_clear_settlement_rule(&env, merchant)
+ }
+ Operation::SetDefaultRule(rule) => Self::_set_default_rule(&env, rule),
}
- pub fn transfer_admin(
- env: Env,
- signers: Vec,
- new_admins: Vec,
- new_threshold: u32,
- ) {
- verify_admin_auth(&env, &signers, read_threshold(&env));
- validate_admins_and_threshold(&env, &new_admins, new_threshold);
-
- let old_admin = read_admin(&env);
- env.storage().instance().set(&DataKey::Admin, &new_admins);
- env.storage()
- .instance()
- .set(&DataKey::Threshold, &new_threshold);
- let primary_new_admin = new_admins.get(0).unwrap();
- events::emit_admin_transferred(
- &env,
- &AdminTransferred {
- old_admin,
- new_admin: primary_new_admin,
- },
- );
+ env.events()
+ .publish((Symbol::new(&env, "op_executed"), op_hash), ());
+ }
+
+ /// Cancels a scheduled administrative operation.
+ pub fn cancel(env: Env, caller: Address, operation: Operation) {
+ let admin = read_admin(&env);
+ if caller != admin {
+ panic_with_error!(&env, SettlementError::Unauthorized);
}
+ caller.require_auth();
- pub fn change_threshold(env: Env, signers: Vec, new_threshold: u32) {
- let current_threshold = read_threshold(&env);
- verify_admin_auth(&env, &signers, current_threshold + 1);
+ let op_hash: BytesN<32> = env.crypto().sha256(&operation.clone().to_xdr(&env)).into();
+ let key = DataKey::ScheduledOperation(op_hash.clone());
- let admins = read_admins(&env);
- if new_threshold == 0 || new_threshold > admins.len() {
- panic_with_error!(&env, SettlementError::InvalidThreshold);
- }
-
- env.storage()
- .instance()
- .set(&DataKey::Threshold, &new_threshold);
- env.events().publish(
- (Symbol::new(&env, "threshold_changed"),),
- (current_threshold, new_threshold),
- );
+ if !env.storage().persistent().has(&key) {
+ panic_with_error!(&env, SettlementError::OperationNotScheduled);
}
- pub fn upgrade(env: Env, signers: Vec, new_wasm_hash: BytesN<32>) {
- verify_admin_auth(&env, &signers, read_threshold(&env));
- let admin = signers.get(0).unwrap();
+ env.storage().persistent().remove(&key);
- let event_wasm_hash = new_wasm_hash.clone();
- env.events().publish(
- (Symbol::new(&env, "contract_upgraded"), event_wasm_hash),
- admin,
- );
+ env.events()
+ .publish((Symbol::new(&env, "op_cancelled"), op_hash), caller);
+ }
- env.deployer().update_current_contract_wasm(new_wasm_hash);
- }
+ // --- Internal Admin Functions ---
- pub fn pause(env: Env, signers: Vec) {
- verify_admin_auth(&env, &signers, read_threshold(&env));
- let admin = signers.get(0).unwrap();
- storage::set_paused(&env, true);
- events::emit_paused(&env, &admin);
+ fn _update_governance(env: &Env, new_governance: Address) {
+ assert_not_paused(env);
+ validate_governance(env, &new_governance);
+ let admin = read_admin(env);
+ env.storage()
+ .instance()
+ .set(&DataKey::Governance, &new_governance);
+ env.events().publish(
+ (Symbol::new(env, "governance_updated"),),
+ (admin, new_governance),
+ );
+ }
+
+ fn _cancel_recovery(env: &Env) {
+ if !env
+ .storage()
+ .instance()
+ .has(&CommonDataKey::PendingRecovery)
+ {
+ panic_with_error!(env, SettlementError::RecoveryNotPending);
}
-
- pub fn unpause(env: Env, signers: Vec) {
- verify_admin_auth(&env, &signers, read_threshold(&env));
- let admin = signers.get(0).unwrap();
- storage::set_paused(&env, false);
- events::emit_unpaused(&env, &admin);
+ let admin = read_admin(env);
+ env.storage()
+ .instance()
+ .remove(&CommonDataKey::PendingRecovery);
+ events::emit_recovery_cancelled(env, &admin);
+ }
+
+ fn _transfer_admin(env: &Env, new_admin: Address) {
+ let admin = read_admin(env);
+ validate_nonzero_address(
+ env,
+ &new_admin,
+ SettlementError::EmptyAddress,
+ SettlementError::ZeroAddress,
+ );
+ if new_admin == admin {
+ panic_with_error!(env, SettlementError::InvalidAdmin);
}
-
- pub fn is_paused(env: Env) -> bool {
- storage::is_paused(&env)
+ env.storage().instance().set(&DataKey::Admin, &new_admin);
+ events::emit_admin_transferred(
+ env,
+ &AdminTransferred {
+ old_admin: admin,
+ new_admin,
+ },
+ );
+ }
+
+ fn _upgrade(env: &Env, new_wasm_hash: BytesN<32>) {
+ let admin = read_admin(env);
+ env.events().publish(
+ (Symbol::new(env, "contract_upgraded"), new_wasm_hash.clone()),
+ admin,
+ );
+ env.deployer().update_current_contract_wasm(new_wasm_hash);
+ }
+
+ fn _register_merchant(env: &Env, merchant: Address) {
+ assert_not_paused(env);
+ validate_nonzero_address(
+ env,
+ &merchant,
+ SettlementError::EmptyAddress,
+ SettlementError::ZeroAddress,
+ );
+ let admin = read_admin(env);
+
+ let key = DataKey::Merchant(merchant.clone());
+ if env.storage().persistent().has(&key) {
+ panic_with_error!(env, SettlementError::MerchantExists);
}
- /// Schedules an administrative operation to be executed after a timelock.
- pub fn schedule(env: Env, caller: Address, operation: Operation, execute_in: u64) {
- let admin = read_admin(&env);
- if caller != admin {
- panic_with_error!(&env, SettlementError::Unauthorized);
- }
- caller.require_auth();
-
- if execute_in < DEFAULT_TIMELOCK_DELAY_SECONDS {
- panic_with_error!(&env, SettlementError::ExecutionNotReady);
- }
-
- let op_hash: BytesN<32> = env.crypto().sha256(&operation.clone().to_xdr(&env)).into();
- let key = DataKey::ScheduledOperation(op_hash.clone());
-
- if env.storage().persistent().has(&key) {
- panic_with_error!(&env, SettlementError::OperationAlreadyScheduled);
- }
+ env.storage().persistent().set(&key, &true);
+ env.storage()
+ .persistent()
+ .extend_ttl(&key, MERCHANT_TTL_THRESHOLD, MERCHANT_TTL_BUMP);
+ env.events()
+ .publish((Symbol::new(env, "merchant_registered"), merchant), admin);
+ }
+
+ fn _unregister_merchant(env: &Env, merchant: Address) {
+ assert_not_paused(env);
+ let admin = read_admin(env);
+
+ let key = DataKey::Merchant(merchant.clone());
+ if !env.storage().persistent().has(&key) {
+ panic_with_error!(env, SettlementError::MerchantMissing);
+ }
- let execute_at = env.ledger().timestamp() + execute_in;
- env.storage().persistent().set(&key, &execute_at);
- env.storage()
- .persistent()
- .extend_ttl(&key, 17280 * 14, 17280 * 30);
+ env.storage().persistent().remove(&key);
+ let rule_key = DataKey::Rule(merchant.clone());
+ let old_rule: Option = env.storage().persistent().get(&rule_key);
+ if let Some(old_rule) = old_rule {
+ env.storage().persistent().remove(&rule_key);
env.events().publish(
- (Symbol::new(&env, "op_scheduled"), op_hash),
- (caller, execute_at),
+ (
+ Symbol::new(env, "settlement_rule_cleared"),
+ merchant.clone(),
+ ),
+ (admin.clone(), old_rule),
);
}
- /// Executes a previously scheduled administrative operation.
- pub fn execute(env: Env, operation: Operation) {
- let op_hash: BytesN<32> = env.crypto().sha256(&operation.clone().to_xdr(&env)).into();
- let key = DataKey::ScheduledOperation(op_hash.clone());
-
- let execute_at: u64 = env
- .storage()
- .persistent()
- .get(&key)
- .unwrap_or_else(|| panic_with_error!(&env, SettlementError::OperationNotScheduled));
-
- if env.ledger().timestamp() < execute_at {
- panic_with_error!(&env, SettlementError::ExecutionNotReady);
- }
-
- env.storage().persistent().remove(&key);
-
- match operation {
- Operation::UpdateGovernance(new_gov) => Self::_update_governance(&env, new_gov),
- Operation::CancelRecovery => {
- let admin = read_admin(&env);
- admin.require_auth();
- Self::_cancel_recovery(&env)
- }
- Operation::TransferAdmin(new_admin) => Self::_transfer_admin(&env, new_admin),
- Operation::Upgrade(wasm_hash) => Self::_upgrade(&env, wasm_hash),
- Operation::RegisterMerchant(merchant) => Self::_register_merchant(&env, merchant),
- Operation::UnregisterMerchant(merchant) => Self::_unregister_merchant(&env, merchant),
- Operation::SetSettlementRule(merchant, rule) => {
- Self::_set_settlement_rule(&env, merchant, rule)
- }
- Operation::ClearSettlementRule(merchant) => {
- Self::_clear_settlement_rule(&env, merchant)
- }
- Operation::SetDefaultRule(rule) => Self::_set_default_rule(&env, rule),
- }
-
- env.events()
- .publish((Symbol::new(&env, "op_executed"), op_hash), ());
- }
-
- /// Cancels a scheduled administrative operation.
- pub fn cancel(env: Env, caller: Address, operation: Operation) {
- let admin = read_admin(&env);
- if caller != admin {
- panic_with_error!(&env, SettlementError::Unauthorized);
- }
- caller.require_auth();
+ env.events()
+ .publish((Symbol::new(env, "merchant_unregistered"), merchant), admin);
+ }
- let op_hash: BytesN<32> = env.crypto().sha256(&operation.clone().to_xdr(&env)).into();
- let key = DataKey::ScheduledOperation(op_hash.clone());
+ fn _set_settlement_rule(env: &Env, merchant: Address, rule: SettlementRule) {
+ assert_not_paused(env);
+ let admin = read_admin(env);
- if !env.storage().persistent().has(&key) {
- panic_with_error!(&env, SettlementError::OperationNotScheduled);
- }
-
- env.storage().persistent().remove(&key);
-
- env.events()
- .publish((Symbol::new(&env, "op_cancelled"), op_hash), caller);
+ if !is_merchant_registered_internal(env, merchant.clone()) {
+ panic_with_error!(env, SettlementError::MerchantMissing);
}
-
-
- // --- Internal Admin Functions ---
-
- fn _update_governance(env: &Env, new_governance: Address) {
- assert_not_paused(env);
- validate_governance(env, &new_governance);
- let admin = read_admin(env);
- env.storage()
- .instance()
- .set(&DataKey::Governance, &new_governance);
- env.events().publish(
- (Symbol::new(env, "governance_updated"),),
- (admin, new_governance),
- );
+ if rule.platform_fee_bps > BPS_DENOMINATOR || rule.network_fee_bps > BPS_DENOMINATOR {
+ panic_with_error!(env, SettlementError::InvalidFeeBps);
}
-
- fn _cancel_recovery(env: &Env) {
- if !env
- .storage()
- .instance()
- .has(&CommonDataKey::PendingRecovery)
- {
- panic_with_error!(env, SettlementError::RecoveryNotPending);
- }
- let admin = read_admin(env);
- env.storage()
- .instance()
- .remove(&CommonDataKey::PendingRecovery);
- events::emit_recovery_cancelled(env, &admin);
+ if rule.platform_fee_bps < MIN_FEE_BPS || rule.network_fee_bps < MIN_FEE_BPS {
+ panic_with_error!(env, SettlementError::InvalidFeeBps);
}
-
- fn _transfer_admin(env: &Env, new_admin: Address) {
- let admin = read_admin(env);
- validate_nonzero_address(
- env,
- &new_admin,
- SettlementError::EmptyAddress,
- SettlementError::ZeroAddress,
- );
- if new_admin == admin {
- panic_with_error!(env, SettlementError::InvalidAdmin);
- }
- env.storage().instance().set(&DataKey::Admin, &new_admin);
- events::emit_admin_transferred(
- env,
- &AdminTransferred {
- old_admin: admin,
- new_admin,
- },
- );
+ if rule.platform_fee_bps > MAX_FEE_BPS || rule.network_fee_bps > MAX_FEE_BPS {
+ panic_with_error!(env, SettlementError::InvalidFeeBps);
}
-
- fn _upgrade(env: &Env, new_wasm_hash: BytesN<32>) {
- let admin = read_admin(env);
- env.events().publish(
- (Symbol::new(env, "contract_upgraded"), new_wasm_hash.clone()),
- admin,
- );
- env.deployer().update_current_contract_wasm(new_wasm_hash);
+ if rule.platform_fee_bps + rule.network_fee_bps > BPS_DENOMINATOR {
+ panic_with_error!(env, SettlementError::InvalidFeeBps);
}
-
- fn _register_merchant(env: &Env, merchant: Address) {
- assert_not_paused(env);
- validate_nonzero_address(
- env,
- &merchant,
- SettlementError::EmptyAddress,
- SettlementError::ZeroAddress,
- );
- let admin = read_admin(env);
-
- let key = DataKey::Merchant(merchant.clone());
- if env.storage().persistent().has(&key) {
- panic_with_error!(env, SettlementError::MerchantExists);
- }
-
- env.storage().persistent().set(&key, &true);
- env.storage()
- .persistent()
- .extend_ttl(&key, MERCHANT_TTL_THRESHOLD, MERCHANT_TTL_BUMP);
- env.events()
- .publish((Symbol::new(env, "merchant_registered"), merchant), admin);
+ if rule.settlement_delay_ledger > MAX_SETTLEMENT_DELAY_LEDGER {
+ panic_with_error!(env, SettlementError::InvalidSettlementDelay);
}
- fn _unregister_merchant(env: &Env, merchant: Address) {
- assert_not_paused(env);
- let admin = read_admin(env);
-
- let key = DataKey::Merchant(merchant.clone());
- if !env.storage().persistent().has(&key) {
- panic_with_error!(env, SettlementError::MerchantMissing);
- }
-
- env.storage().persistent().remove(&key);
-
- let rule_key = DataKey::Rule(merchant.clone());
- let old_rule: Option = env.storage().persistent().get(&rule_key);
- if let Some(old_rule) = old_rule {
- env.storage().persistent().remove(&rule_key);
- env.events().publish(
- (
- Symbol::new(env, "settlement_rule_cleared"),
- merchant.clone(),
- ),
- (admin.clone(), old_rule),
- );
- }
-
- env.events()
- .publish((Symbol::new(env, "merchant_unregistered"), merchant), admin);
+ let prev = env
+ .storage()
+ .persistent()
+ .get::<_, SettlementRule>(&DataKey::Rule(merchant.clone()))
+ .unwrap_or_else(|| read_rule_or_default(env, merchant.clone()));
+
+ let key = DataKey::Rule(merchant.clone());
+ env.storage().persistent().set(&key, &rule);
+ env.storage()
+ .persistent()
+ .extend_ttl(&key, RULE_TTL_THRESHOLD, RULE_TTL_BUMP);
+
+ env.events().publish(
+ (Symbol::new(env, "settlement_rule_updated"), merchant),
+ (admin, prev, rule),
+ );
+ }
+
+ fn _clear_settlement_rule(env: &Env, merchant: Address) {
+ assert_not_paused(env);
+ let admin = read_admin(env);
+
+ let key = DataKey::Rule(merchant.clone());
+ let removed = env
+ .storage()
+ .persistent()
+ .get::<_, SettlementRule>(&key)
+ .unwrap_or_else(|| panic_with_error!(env, SettlementError::MerchantRuleNotSet));
+
+ env.storage().persistent().remove(&key);
+
+ let fallback = read_rule_or_default(env, merchant.clone());
+
+ env.events().publish(
+ (Symbol::new(env, "settlement_rule_cleared"), merchant),
+ (admin, removed, fallback),
+ );
+ }
+
+ fn _set_default_rule(env: &Env, new_rule: SettlementRule) {
+ assert_not_paused(env);
+ let admin = read_admin(env);
+
+ if new_rule.platform_fee_bps > BPS_DENOMINATOR || new_rule.network_fee_bps > BPS_DENOMINATOR
+ {
+ panic_with_error!(env, SettlementError::InvalidFeeBps);
}
-
- fn _set_settlement_rule(env: &Env, merchant: Address, rule: SettlementRule) {
- assert_not_paused(env);
- let admin = read_admin(env);
-
- if !is_merchant_registered_internal(env, merchant.clone()) {
- panic_with_error!(env, SettlementError::MerchantMissing);
- }
- if rule.platform_fee_bps > BPS_DENOMINATOR || rule.network_fee_bps > BPS_DENOMINATOR {
- panic_with_error!(env, SettlementError::InvalidFeeBps);
- }
- if rule.platform_fee_bps < MIN_FEE_BPS || rule.network_fee_bps < MIN_FEE_BPS {
- panic_with_error!(env, SettlementError::InvalidFeeBps);
- }
- if rule.platform_fee_bps > MAX_FEE_BPS || rule.network_fee_bps > MAX_FEE_BPS {
- panic_with_error!(env, SettlementError::InvalidFeeBps);
- }
- if rule.platform_fee_bps + rule.network_fee_bps > BPS_DENOMINATOR {
- panic_with_error!(env, SettlementError::InvalidFeeBps);
- }
- if rule.settlement_delay_ledger > MAX_SETTLEMENT_DELAY_LEDGER {
- panic_with_error!(env, SettlementError::InvalidSettlementDelay);
- }
-
- let prev = env
- .storage()
- .persistent()
- .get::<_, SettlementRule>(&DataKey::Rule(merchant.clone()))
- .unwrap_or_else(|| read_rule_or_default(env, merchant.clone()));
-
- let key = DataKey::Rule(merchant.clone());
- env.storage().persistent().set(&key, &rule);
- env.storage()
- .persistent()
- .extend_ttl(&key, RULE_TTL_THRESHOLD, RULE_TTL_BUMP);
-
- env.events().publish(
- (Symbol::new(env, "settlement_rule_updated"), merchant),
- (admin, prev, rule),
- );
+ if new_rule.platform_fee_bps < MIN_FEE_BPS || new_rule.network_fee_bps < MIN_FEE_BPS {
+ panic_with_error!(env, SettlementError::InvalidFeeBps);
}
-
- fn _clear_settlement_rule(env: &Env, merchant: Address) {
- assert_not_paused(env);
- let admin = read_admin(env);
-
- let key = DataKey::Rule(merchant.clone());
- let removed = env
- .storage()
- .persistent()
- .get::<_, SettlementRule>(&key)
- .unwrap_or_else(|| panic_with_error!(env, SettlementError::MerchantRuleNotSet));
-
- env.storage().persistent().remove(&key);
-
- let fallback = read_rule_or_default(env, merchant.clone());
-
- env.events().publish(
- (Symbol::new(env, "settlement_rule_cleared"), merchant),
- (admin, removed, fallback),
- );
+ if new_rule.platform_fee_bps > MAX_FEE_BPS || new_rule.network_fee_bps > MAX_FEE_BPS {
+ panic_with_error!(env, SettlementError::InvalidFeeBps);
}
-
- fn _set_default_rule(env: &Env, new_rule: SettlementRule) {
- assert_not_paused(env);
- let admin = read_admin(env);
-
- if new_rule.platform_fee_bps > BPS_DENOMINATOR || new_rule.network_fee_bps > BPS_DENOMINATOR
- {
- panic_with_error!(env, SettlementError::InvalidFeeBps);
- }
- if new_rule.platform_fee_bps < MIN_FEE_BPS || new_rule.network_fee_bps < MIN_FEE_BPS {
- panic_with_error!(env, SettlementError::InvalidFeeBps);
- }
- if new_rule.platform_fee_bps > MAX_FEE_BPS || new_rule.network_fee_bps > MAX_FEE_BPS {
- panic_with_error!(env, SettlementError::InvalidFeeBps);
- }
- if new_rule.settlement_delay_ledger > MAX_SETTLEMENT_DELAY_LEDGER {
- panic_with_error!(env, SettlementError::InvalidSettlementDelay);
- }
-
- let prev = env
- .storage()
- .persistent()
- .get::<_, SettlementRule>(&DataKey::DefaultRule)
- .unwrap_or(BOOTSTRAP_DEFAULT_RULE);
-
- env.storage()
- .persistent()
- .set(&DataKey::DefaultRule, &new_rule);
- env.storage().persistent().extend_ttl(
- &DataKey::DefaultRule,
- RULE_TTL_THRESHOLD,
- RULE_TTL_BUMP,
- );
-
- env.events().publish(
- (Symbol::new(env, "default_rule_updated"),),
- (admin, prev, new_rule),
- );
+ if new_rule.settlement_delay_ledger > MAX_SETTLEMENT_DELAY_LEDGER {
+ panic_with_error!(env, SettlementError::InvalidSettlementDelay);
}
+ let prev = env
+ .storage()
+ .persistent()
+ .get::<_, SettlementRule>(&DataKey::DefaultRule)
+ .unwrap_or(BOOTSTRAP_DEFAULT_RULE);
+
+ env.storage()
+ .persistent()
+ .set(&DataKey::DefaultRule, &new_rule);
+ env.storage().persistent().extend_ttl(
+ &DataKey::DefaultRule,
+ RULE_TTL_THRESHOLD,
+ RULE_TTL_BUMP,
+ );
+
+ env.events().publish(
+ (Symbol::new(env, "default_rule_updated"),),
+ (admin, prev, new_rule),
+ );
+ }
}
diff --git a/settlement_contract/src/errors.rs b/settlement_contract/src/errors.rs
index 025a4c6..8e9a803 100644
--- a/settlement_contract/src/errors.rs
+++ b/settlement_contract/src/errors.rs
@@ -87,9 +87,7 @@ const _: () = {
assert!(SettlementError::RecoveryNotPending as u32 == error_codes::RECOVERY_NOT_PENDING);
assert!(SettlementError::RecoveryDelayActive as u32 == error_codes::RECOVERY_DELAY_ACTIVE);
assert!(SettlementError::ExecutionNotReady as u32 == error_codes::EXECUTION_NOT_READY);
- assert!(
- SettlementError::OperationNotScheduled as u32 == error_codes::OPERATION_NOT_SCHEDULED
- );
+ assert!(SettlementError::OperationNotScheduled as u32 == error_codes::OPERATION_NOT_SCHEDULED);
assert!(
SettlementError::OperationAlreadyScheduled as u32
== error_codes::OPERATION_ALREADY_SCHEDULED
@@ -104,18 +102,13 @@ const _: () = {
assert!(SettlementError::MerchantRuleNotSet as u32 >= error_codes::SETTLEMENT_RANGE_START);
assert!(SettlementError::EmptyAddress as u32 >= error_codes::SETTLEMENT_RANGE_START);
assert!(SettlementError::ZeroAddress as u32 >= error_codes::SETTLEMENT_RANGE_START);
- assert!(
- SettlementError::InvalidPaymentReference as u32 >= error_codes::SETTLEMENT_RANGE_START
- );
- assert!(
- SettlementError::InvalidSettlementDelay as u32 >= error_codes::SETTLEMENT_RANGE_START
- );
+ assert!(SettlementError::InvalidPaymentReference as u32 >= error_codes::SETTLEMENT_RANGE_START);
+ assert!(SettlementError::InvalidSettlementDelay as u32 >= error_codes::SETTLEMENT_RANGE_START);
assert!(SettlementError::InvalidGovernance as u32 >= error_codes::SETTLEMENT_RANGE_START);
assert!(SettlementError::AmountOverflow as u32 >= error_codes::SETTLEMENT_RANGE_START);
assert!(SettlementError::GovernanceCallFailed as u32 >= error_codes::SETTLEMENT_RANGE_START);
assert!(
- SettlementError::FeeExceedsGovernanceConfig as u32
- >= error_codes::SETTLEMENT_RANGE_START
+ SettlementError::FeeExceedsGovernanceConfig as u32 >= error_codes::SETTLEMENT_RANGE_START
);
assert!(SettlementError::AmountTooSmall as u32 >= error_codes::SETTLEMENT_RANGE_START);
assert!(SettlementError::AmountZero as u32 >= error_codes::SETTLEMENT_RANGE_START);
diff --git a/settlement_contract/src/merchant.rs b/settlement_contract/src/merchant.rs
index 85affb9..00c1967 100644
--- a/settlement_contract/src/merchant.rs
+++ b/settlement_contract/src/merchant.rs
@@ -12,73 +12,72 @@ use crate::{
#[contractimpl]
impl SettlementContract {
- pub fn register_merchant(env: Env, signers: Vec, merchant: Address) {
- assert_not_paused(&env);
+ pub fn register_merchant(env: Env, signers: Vec, merchant: Address) {
+ assert_not_paused(&env);
- validate_nonzero_address(
- &env,
- &merchant,
- SettlementError::EmptyAddress,
- SettlementError::ZeroAddress,
- );
-
- verify_admin_auth(&env, &signers, read_threshold(&env));
- let admin = signers.get(0).unwrap();
+ validate_nonzero_address(
+ &env,
+ &merchant,
+ SettlementError::EmptyAddress,
+ SettlementError::ZeroAddress,
+ );
- let key = DataKey::Merchant(merchant.clone());
- if env.storage().persistent().has(&key) {
- panic_with_error!(&env, SettlementError::MerchantExists);
- }
+ verify_admin_auth(&env, &signers, read_threshold(&env));
+ let admin = signers.get(0).unwrap();
- env.storage().persistent().set(&key, &());
- env.storage()
- .persistent()
- .extend_ttl(&key, MERCHANT_TTL_THRESHOLD, MERCHANT_TTL_BUMP);
- env.events()
- .publish((Symbol::new(&env, "merchant_registered"), merchant), admin);
+ let key = DataKey::Merchant(merchant.clone());
+ if env.storage().persistent().has(&key) {
+ panic_with_error!(&env, SettlementError::MerchantExists);
}
- pub fn unregister_merchant(env: Env, signers: Vec, merchant: Address) {
- assert_not_paused(&env);
- verify_admin_auth(&env, &signers, read_threshold(&env));
- let admin = signers.get(0).unwrap();
+ env.storage().persistent().set(&key, &());
+ env.storage()
+ .persistent()
+ .extend_ttl(&key, MERCHANT_TTL_THRESHOLD, MERCHANT_TTL_BUMP);
+ env.events()
+ .publish((Symbol::new(&env, "merchant_registered"), merchant), admin);
+ }
- let key = DataKey::Merchant(merchant.clone());
- if !env.storage().persistent().has(&key) {
- panic_with_error!(&env, SettlementError::MerchantMissing);
- }
+ pub fn unregister_merchant(env: Env, signers: Vec, merchant: Address) {
+ assert_not_paused(&env);
+ verify_admin_auth(&env, &signers, read_threshold(&env));
+ let admin = signers.get(0).unwrap();
- env.storage().persistent().remove(&key);
+ let key = DataKey::Merchant(merchant.clone());
+ if !env.storage().persistent().has(&key) {
+ panic_with_error!(&env, SettlementError::MerchantMissing);
+ }
- let rule_key = DataKey::Rule(merchant.clone());
- let old_rule: Option = env.storage().persistent().get(&rule_key);
- if let Some(old_rule) = old_rule {
- env.storage().persistent().remove(&rule_key);
- env.events().publish(
- (
- Symbol::new(&env, "settlement_rule_cleared"),
- merchant.clone(),
- ),
- (admin.clone(), old_rule),
- );
- }
+ env.storage().persistent().remove(&key);
+ let rule_key = DataKey::Rule(merchant.clone());
+ let old_rule: Option = env.storage().persistent().get(&rule_key);
+ if let Some(old_rule) = old_rule {
+ env.storage().persistent().remove(&rule_key);
env.events().publish(
- (Symbol::new(&env, "merchant_unregistered"), merchant),
- admin,
+ (
+ Symbol::new(&env, "settlement_rule_cleared"),
+ merchant.clone(),
+ ),
+ (admin.clone(), old_rule),
);
}
- /// Returns `true` if the given address is a registered merchant, `false` otherwise.
- ///
- /// # Panics
- ///
- /// * [`NotInitialized`](SettlementError::NotInitialized) — if the contract has not been initialized yet.
- pub fn is_merchant_registered(env: Env, merchant: Address) -> bool {
- if !env.storage().instance().has(&DataKey::Admin) {
- panic_with_error!(&env, SettlementError::NotInitialized);
- }
- is_merchant_registered_internal(&env, merchant)
- }
+ env.events().publish(
+ (Symbol::new(&env, "merchant_unregistered"), merchant),
+ admin,
+ );
+ }
+ /// Returns `true` if the given address is a registered merchant, `false` otherwise.
+ ///
+ /// # Panics
+ ///
+ /// * [`NotInitialized`](SettlementError::NotInitialized) — if the contract has not been initialized yet.
+ pub fn is_merchant_registered(env: Env, merchant: Address) -> bool {
+ if !env.storage().instance().has(&DataKey::Admin) {
+ panic_with_error!(&env, SettlementError::NotInitialized);
+ }
+ is_merchant_registered_internal(&env, merchant)
+ }
}
diff --git a/settlement_contract/src/payments.rs b/settlement_contract/src/payments.rs
index 66fa5fd..3e904c6 100644
--- a/settlement_contract/src/payments.rs
+++ b/settlement_contract/src/payments.rs
@@ -57,137 +57,136 @@ fn calculate_split(env: &Env, amount: i128, rule: &SettlementRule) -> FeeSplit {
#[contractimpl]
impl SettlementContract {
- /// Store a payment reference for a merchant and calculate the fee split.
- ///
- /// # Panics
- ///
- /// * [`Paused`](SettlementError::Paused) — if the contract is paused.
- /// * [`MerchantMissing`](SettlementError::MerchantMissing) — if the merchant is not registered.
- /// * [`InvalidPaymentReference`](SettlementError::InvalidPaymentReference) — if `reference` is all zeros.
- /// * [`AmountTooSmall`](SettlementError::AmountTooSmall) — if `amount` is below the minimum.
- /// * [`DuplicatePaymentReference`](SettlementError::DuplicatePaymentReference) — if the reference already exists.
- /// * [`AmountOverflow`](SettlementError::AmountOverflow) — if `amount * bps` would overflow `i128`.
- ///
- /// ## Emitted Event: `payment_stored`
- ///
- /// **Topics**: `(Symbol("payment_stored"), Address merchant, BytesN<32> reference)`
- /// **Data**: `()`
- ///
- /// The fee split (platform fee, network fee, merchant amount, gross amount)
- /// is available on the `PaymentRecord` in this event's data; no separate
- /// split event is emitted.
- pub fn store_payment_reference(
- env: Env,
- merchant: Address,
- reference: BytesN<32>,
- amount: i128,
- ) -> FeeSplit {
- assert_not_paused(&env);
-
- if !is_merchant_registered_internal(&env, merchant.clone()) {
- panic_with_error!(&env, SettlementError::MerchantMissing);
- }
- merchant.require_auth();
- if reference == BytesN::from_array(&env, &[0; 32]) {
- panic_with_error!(&env, SettlementError::InvalidPaymentReference);
- }
- if amount < MIN_PAYMENT_AMOUNT {
- panic_with_error!(&env, SettlementError::AmountTooSmall);
- }
-
- let payment_key = DataKey::Payment(reference.clone());
- if env.storage().persistent().has(&payment_key) {
- panic_with_error!(&env, SettlementError::DuplicatePaymentReference);
- }
-
- let rule = read_rule_or_default(&env, merchant.clone());
- let split = calculate_split(&env, amount, &rule);
- let record = PaymentRecord {
- amount,
- platform_fee_amount: split.platform_fee_amount,
- network_fee_amount: split.network_fee_amount,
- merchant_amount: split.merchant_amount,
- platform_fee_bps: rule.platform_fee_bps,
- network_fee_bps: rule.network_fee_bps,
- ledger: env.ledger().sequence(),
- settlement_delay_ledger: rule.settlement_delay_ledger,
- auto_settle: rule.auto_settle,
- };
-
- env.storage().persistent().set(&payment_key, &record);
- env.storage().persistent().extend_ttl(
- &payment_key,
- PAYMENT_TTL_THRESHOLD,
- PAYMENT_TTL_BUMP,
- );
-
- env.events().publish(
- (
- Symbol::new(&env, "payment_stored"),
- merchant.clone(),
- reference.clone(),
- ),
- (),
- );
-
- split
+ /// Store a payment reference for a merchant and calculate the fee split.
+ ///
+ /// # Panics
+ ///
+ /// * [`Paused`](SettlementError::Paused) — if the contract is paused.
+ /// * [`MerchantMissing`](SettlementError::MerchantMissing) — if the merchant is not registered.
+ /// * [`InvalidPaymentReference`](SettlementError::InvalidPaymentReference) — if `reference` is all zeros.
+ /// * [`AmountTooSmall`](SettlementError::AmountTooSmall) — if `amount` is below the minimum.
+ /// * [`DuplicatePaymentReference`](SettlementError::DuplicatePaymentReference) — if the reference already exists.
+ /// * [`AmountOverflow`](SettlementError::AmountOverflow) — if `amount * bps` would overflow `i128`.
+ ///
+ /// ## Emitted Event: `payment_stored`
+ ///
+ /// **Topics**: `(Symbol("payment_stored"), Address merchant, BytesN<32> reference)`
+ /// **Data**: `()`
+ ///
+ /// The fee split (platform fee, network fee, merchant amount, gross amount)
+ /// is available on the `PaymentRecord` in this event's data; no separate
+ /// split event is emitted.
+ pub fn store_payment_reference(
+ env: Env,
+ merchant: Address,
+ reference: BytesN<32>,
+ amount: i128,
+ ) -> FeeSplit {
+ assert_not_paused(&env);
+
+ if !is_merchant_registered_internal(&env, merchant.clone()) {
+ panic_with_error!(&env, SettlementError::MerchantMissing);
+ }
+ merchant.require_auth();
+ if reference == BytesN::from_array(&env, &[0; 32]) {
+ panic_with_error!(&env, SettlementError::InvalidPaymentReference);
+ }
+ if amount < MIN_PAYMENT_AMOUNT {
+ panic_with_error!(&env, SettlementError::AmountTooSmall);
}
- /// Calculate the fee split for a given merchant and amount without storing a payment reference.
- ///
- /// # Panics
- ///
- /// * [`MerchantMissing`](SettlementError::MerchantMissing) — if the merchant is not registered.
- /// * [`AmountZero`](SettlementError::AmountZero) — if `amount` is zero.
- /// * [`AmountNegative`](SettlementError::AmountNegative) — if `amount` is negative.
- /// * [`AmountOverflow`](SettlementError::AmountOverflow) — if `amount * bps` would overflow `i128`.
- pub fn calculate_fee_split(env: Env, merchant: Address, amount: i128) -> FeeSplit {
- if !is_merchant_registered_internal(&env, merchant.clone()) {
- panic_with_error!(&env, SettlementError::MerchantMissing);
- }
- if amount == 0 {
- panic_with_error!(&env, SettlementError::AmountZero);
- }
- if amount < 0 {
- panic_with_error!(&env, SettlementError::AmountNegative);
- }
- let rule = read_rule_or_default(&env, merchant);
- calculate_split(&env, amount, &rule)
+ let payment_key = DataKey::Payment(reference.clone());
+ if env.storage().persistent().has(&payment_key) {
+ panic_with_error!(&env, SettlementError::DuplicatePaymentReference);
}
- /// Retrieve a payment record by its reference, extending the storage TTL if found.
- pub fn get_payment_reference(env: Env, reference: BytesN<32>) -> Option {
- let key = DataKey::Payment(reference);
- let record: Option = env.storage().persistent().get(&key);
- if record.is_some() {
- // `extend_ttl` only writes when the current TTL is below
- // `threshold`, so this has the same externally observable
- // behavior as a manual get_ttl-then-extend check, without
- // depending on `get_ttl`, which is test-only in production code.
- env.storage()
- .persistent()
- .extend_ttl(&key, PAYMENT_TTL_THRESHOLD, PAYMENT_TTL_BUMP);
- }
- record
+ let rule = read_rule_or_default(&env, merchant.clone());
+ let split = calculate_split(&env, amount, &rule);
+ let record = PaymentRecord {
+ amount,
+ platform_fee_amount: split.platform_fee_amount,
+ network_fee_amount: split.network_fee_amount,
+ merchant_amount: split.merchant_amount,
+ platform_fee_bps: rule.platform_fee_bps,
+ network_fee_bps: rule.network_fee_bps,
+ ledger: env.ledger().sequence(),
+ settlement_delay_ledger: rule.settlement_delay_ledger,
+ auto_settle: rule.auto_settle,
+ };
+
+ env.storage().persistent().set(&payment_key, &record);
+ env.storage().persistent().extend_ttl(
+ &payment_key,
+ PAYMENT_TTL_THRESHOLD,
+ PAYMENT_TTL_BUMP,
+ );
+
+ env.events().publish(
+ (
+ Symbol::new(&env, "payment_stored"),
+ merchant.clone(),
+ reference.clone(),
+ ),
+ (),
+ );
+
+ split
+ }
+
+ /// Calculate the fee split for a given merchant and amount without storing a payment reference.
+ ///
+ /// # Panics
+ ///
+ /// * [`MerchantMissing`](SettlementError::MerchantMissing) — if the merchant is not registered.
+ /// * [`AmountZero`](SettlementError::AmountZero) — if `amount` is zero.
+ /// * [`AmountNegative`](SettlementError::AmountNegative) — if `amount` is negative.
+ /// * [`AmountOverflow`](SettlementError::AmountOverflow) — if `amount * bps` would overflow `i128`.
+ pub fn calculate_fee_split(env: Env, merchant: Address, amount: i128) -> FeeSplit {
+ if !is_merchant_registered_internal(&env, merchant.clone()) {
+ panic_with_error!(&env, SettlementError::MerchantMissing);
+ }
+ if amount == 0 {
+ panic_with_error!(&env, SettlementError::AmountZero);
+ }
+ if amount < 0 {
+ panic_with_error!(&env, SettlementError::AmountNegative);
}
+ let rule = read_rule_or_default(&env, merchant);
+ calculate_split(&env, amount, &rule)
+ }
- /// Retrieve multiple payment records by a vector of references.
- ///
- /// The returned vector preserves the input order. Each entry is `Some(payment)`
- /// when a record exists for the corresponding reference and `None` otherwise.
- pub fn get_payments(env: Env, references: Vec>) -> Vec