I would like to understand how treasury accounts are created and used under the hood
You can see here for a similar explanation on sub-accounts.
To walk through the specific steps here:
Here is the function defined in the Treasury pallet for generating the account id:
/// The account ID of the treasury pot. /// /// This actually does computation. If you need to keep using it, then make sure you cache the /// value and only call this once. pub fn account_id() -> T::AccountId { T::PalletId::get().into_account() }
T::PalletId::get()
is defined as:pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
into_account()
is defined as:/// Convert into an account ID. This is infallible. fn into_account(&self) -> AccountId { self.into_sub_account(&()) } fn into_sub_account<S: Encode>(&self, sub: S) -> T { (Id::TYPE_ID, self, sub) .using_encoded(|b| T::decode(&mut TrailingZeroInput(b))) .expect("`AccountId` type is never greater than 32 bytes; qed") }
Id::TYPE_ID
is defined as:impl TypeId for PalletId { const TYPE_ID: [u8; 4] = *b"modl"; }
So to generate the account, we just need to take the raw bytes of "modl" + "py/trsry" and stick that into an account:
as_bytes("modl") = 0x6d6f646c
as_bytes("py/trsry") = 0x70792f7472737279
So the Account Id is:
0x6d6f646c70792f74727372790000000000000000000000000000000000000000
Which maps to Substrate Address:
5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z
Check it out on Subscan:
This public key has no known private key since it is just generated from raw bytes as shown above. When accessing funds from this account, we dontdon't use any cryptography. The low level runtime has direct access to modify this account in any way without authorization.
If you want to look for the private key, you can try, and if you succeed, you will get access to all the treasury funds across all Substrate chains.
A pretty nice bounty, so do let me know when you find it :)