system_instruction.sol 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. // SPDX-License-Identifier: Apache-2.0
  2. // Disclaimer: This library provides a bridge for Solidity to interact with Solana's system instructions. Although it is production ready,
  3. // it has not been audited for security, so use it at your own risk.
  4. import 'solana';
  5. library SystemInstruction {
  6. address constant systemAddress = address"11111111111111111111111111111111";
  7. address constant recentBlockHashes = address"SysvarRecentB1ockHashes11111111111111111111";
  8. address constant rentAddress = address"SysvarRent111111111111111111111111111111111";
  9. uint64 constant state_size = 80;
  10. enum Instruction {
  11. CreateAccount,
  12. Assign,
  13. Transfer,
  14. CreateAccountWithSeed,
  15. AdvanceNounceAccount,
  16. WithdrawNonceAccount,
  17. InitializeNonceAccount,
  18. AuthorizeNonceAccount,
  19. Allocate,
  20. AllocateWithSeed,
  21. AssignWithSeed,
  22. TransferWithSeed,
  23. UpgradeNonceAccount // This is not available on Solana v1.9.15
  24. }
  25. /// Create a new account on Solana
  26. ///
  27. /// @param from public key for the account from which to transfer lamports to the new account
  28. /// @param to public key for the account to be created
  29. /// @param lamports amount of lamports to be transfered to the new account
  30. /// @param space the size in bytes that is going to be made available for the account
  31. /// @param owner public key for the program that will own the account being created
  32. function create_account(address from, address to, uint64 lamports, uint64 space, address owner) internal view {
  33. AccountMeta[2] metas = [
  34. AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
  35. AccountMeta({pubkey: to, is_signer: true, is_writable: true})
  36. ];
  37. bytes bincode = abi.encode(uint32(Instruction.CreateAccount), lamports, space, owner);
  38. systemAddress.call{accounts: metas}(bincode);
  39. }
  40. /// Create a new account on Solana using a public key derived from a seed
  41. ///
  42. /// @param from public key for the account from which to transfer lamports to the new account
  43. /// @param to the public key for the account to be created. The public key must match create_with_seed(base, seed, owner)
  44. /// @param base the base address that derived the 'to' address using the seed
  45. /// @param seed the string utilized to created the 'to' public key
  46. /// @param lamports amount of lamports to be transfered to the new account
  47. /// @param space the size in bytes that is going to be made available for the account
  48. /// @param owner public key for the program that will own the account being created
  49. function create_account_with_seed(address from, address to, address base, string seed, uint64 lamports, uint64 space, address owner) internal view {
  50. AccountMeta[3] metas = [
  51. AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
  52. AccountMeta({pubkey: to, is_signer: false, is_writable: true}),
  53. AccountMeta({pubkey: base, is_signer: true, is_writable: false})
  54. ];
  55. uint32 buffer_size = 92 + seed.length;
  56. bytes bincode = new bytes(buffer_size);
  57. bincode.writeUint32LE(uint32(Instruction.CreateAccountWithSeed), 0);
  58. bincode.writeAddress(base, 4);
  59. bincode.writeUint64LE(uint64(seed.length), 36);
  60. bincode.writeString(seed, 44);
  61. uint32 offset = seed.length + 44;
  62. bincode.writeUint64LE(lamports, offset);
  63. offset += 8;
  64. bincode.writeUint64LE(space, offset);
  65. offset += 8;
  66. bincode.writeAddress(owner, offset);
  67. systemAddress.call{accounts: metas}(bincode);
  68. }
  69. /// Assign account to a program (owner)
  70. ///
  71. /// @param pubkey the public key for the account whose owner is going to be reassigned
  72. /// @param owner the public key for the new account owner
  73. function assign(address pubkey, address owner) internal view {
  74. AccountMeta[1] meta = [
  75. AccountMeta({pubkey: pubkey, is_signer: true, is_writable: true})
  76. ];
  77. bytes bincode = abi.encode(uint32(Instruction.Assign), owner);
  78. systemAddress.call{accounts: meta}(bincode);
  79. }
  80. /// Assign account to a program (owner) based on a seed
  81. ///
  82. /// @param addr the public key for the account whose owner is going to be reassigned. The public key must match create_with_seed(base, seed, owner)
  83. /// @param base the base address that derived the 'addr' key using the seed
  84. /// @param seed the string utilized to created the 'addr' public key
  85. /// @param owner the public key for the new program owner
  86. function assign_with_seed(address addr, address base, string seed, address owner) internal view {
  87. AccountMeta[2] metas = [
  88. AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
  89. AccountMeta({pubkey: base, is_signer: true, is_writable: false})
  90. ];
  91. uint32 buffer_size = 76 + seed.length;
  92. bytes bincode = new bytes(buffer_size);
  93. bincode.writeUint32LE(uint32(Instruction.AssignWithSeed), 0);
  94. bincode.writeAddress(base, 4);
  95. bincode.writeUint64LE(uint64(seed.length), 36);
  96. bincode.writeString(seed, 44);
  97. bincode.writeAddress(owner, 44 + seed.length);
  98. systemAddress.call{accounts: metas}(bincode);
  99. }
  100. /// Transfer lamports between accounts
  101. ///
  102. /// @param from public key for the funding account
  103. /// @param to public key for the recipient account
  104. /// @param lamports amount of lamports to transfer
  105. function transfer(address from, address to, uint64 lamports) internal view {
  106. AccountMeta[2] metas = [
  107. AccountMeta({pubkey: from, is_signer: true, is_writable: true}),
  108. AccountMeta({pubkey: to, is_signer: false, is_writable: true})
  109. ];
  110. bytes bincode = abi.encode(uint32(Instruction.Transfer), lamports);
  111. systemAddress.call{accounts: metas}(bincode);
  112. }
  113. /// Transfer lamports from a derived address
  114. ///
  115. /// @param from_pubkey The funding account public key. It should match create_with_seed(from_base, seed, from_owner)
  116. /// @param from_base the base address that derived the 'from_pubkey' key using the seed
  117. /// @param seed the string utilized to create the 'from_pubkey' public key
  118. /// @param from_owner owner to use to derive the funding account address
  119. /// @param to_pubkey the public key for the recipient account
  120. /// @param lamports amount of lamports to transfer
  121. function transfer_with_seed(address from_pubkey, address from_base, string seed, address from_owner, address to_pubkey, uint64 lamports) internal view {
  122. AccountMeta[3] metas = [
  123. AccountMeta({pubkey: from_pubkey, is_signer: false, is_writable: true}),
  124. AccountMeta({pubkey: from_base, is_signer: true, is_writable: false}),
  125. AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true})
  126. ];
  127. uint32 buffer_size = seed.length + 52;
  128. bytes bincode = new bytes(buffer_size);
  129. bincode.writeUint32LE(uint32(Instruction.TransferWithSeed), 0);
  130. bincode.writeUint64LE(lamports, 4);
  131. bincode.writeUint64LE(seed.length, 12);
  132. bincode.writeString(seed, 20);
  133. bincode.writeAddress(from_owner, 20 + seed.length);
  134. systemAddress.call{accounts: metas}(bincode);
  135. }
  136. /// Allocate space in a (possibly new) account without funding
  137. ///
  138. /// @param pub_key account for which to allocate space
  139. /// @param space number of bytes of memory to allocate
  140. function allocate(address pub_key, uint64 space) internal view {
  141. AccountMeta[1] meta = [
  142. AccountMeta({pubkey: pub_key, is_signer: true, is_writable: true})
  143. ];
  144. bytes bincode = abi.encode(uint32(Instruction.Allocate), space);
  145. systemAddress.call{accounts: meta}(bincode);
  146. }
  147. /// Allocate space for an assign an account at an address derived from a base public key and a seed
  148. ///
  149. /// @param addr account for which to allocate space. It should match create_with_seed(base, seed, owner)
  150. /// @param base the base address that derived the 'addr' key using the seed
  151. /// @param seed the string utilized to create the 'addr' public key
  152. /// @param space number of bytes of memory to allocate
  153. /// @param owner owner to use to derive the 'addr' account address
  154. function allocate_with_seed(address addr, address base, string seed, uint64 space, address owner) internal view {
  155. AccountMeta[2] metas = [
  156. AccountMeta({pubkey: addr, is_signer: false, is_writable: true}),
  157. AccountMeta({pubkey: base, is_signer: true, is_writable: false})
  158. ];
  159. bytes bincode = new bytes(seed.length + 84);
  160. bincode.writeUint32LE(uint32(Instruction.AllocateWithSeed), 0);
  161. bincode.writeAddress(base, 4);
  162. bincode.writeUint64LE(seed.length, 36);
  163. bincode.writeString(seed, 44);
  164. uint32 offset = 44 + seed.length;
  165. bincode.writeUint64LE(space, offset);
  166. offset += 8;
  167. bincode.writeAddress(owner, offset);
  168. systemAddress.call{accounts: metas}(bincode);
  169. }
  170. /// Create a new nonce account on Solana using a public key derived from a seed
  171. ///
  172. /// @param from public key for the account from which to transfer lamports to the new account
  173. /// @param nonce the public key for the account to be created. The public key must match create_with_seed(base, seed, systemAddress)
  174. /// @param base the base address that derived the 'nonce' key using the seed
  175. /// @param seed the string utilized to create the 'addr' public key
  176. /// @param authority The entity authorized to execute nonce instructions on the account
  177. /// @param lamports amount of lamports to be transfered to the new account
  178. function create_nonce_account_with_seed(address from, address nonce, address base, string seed, address authority, uint64 lamports) internal view {
  179. create_account_with_seed(from, nonce, base, seed, lamports, state_size, systemAddress);
  180. AccountMeta[3] metas = [
  181. AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
  182. AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
  183. AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
  184. ];
  185. bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
  186. systemAddress.call{accounts: metas}(bincode);
  187. }
  188. /// Create a new account on Solana
  189. ///
  190. /// @param from public key for the account from which to transfer lamports to the new account
  191. /// @param nonce the public key for the nonce account to be created
  192. /// @param authority The entity authorized to execute nonce instructions on the account
  193. /// @param lamports amount of lamports to be transfered to the new account
  194. function create_nonce_account(address from, address nonce, address authority, uint64 lamports) internal view {
  195. create_account(from, nonce, lamports, state_size, systemAddress);
  196. AccountMeta[3] metas = [
  197. AccountMeta({pubkey: nonce, is_signer: false, is_writable: true}),
  198. AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
  199. AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false})
  200. ];
  201. bytes bincode = abi.encode(uint32(Instruction.InitializeNonceAccount), authority);
  202. systemAddress.call{accounts: metas}(bincode);
  203. }
  204. /// Consumes a stored nonce, replacing it with a successor
  205. ///
  206. /// @param nonce_pubkey the public key for the nonce account
  207. /// @param authorized_pubkey the publick key for the entity authorized to execute instructins on the account
  208. function advance_nonce_account(address nonce_pubkey, address authorized_pubkey) internal view {
  209. AccountMeta[3] metas = [
  210. AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
  211. AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
  212. AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
  213. ];
  214. bytes bincode = abi.encode(uint32(Instruction.AdvanceNounceAccount));
  215. systemAddress.call{accounts: metas}(bincode);
  216. }
  217. /// Withdraw funds from a nonce account
  218. ///
  219. /// @param nonce_pubkey the public key for the nonce account
  220. /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
  221. /// @param to_pubkey the recipient account
  222. /// @param lamports the number of lamports to withdraw
  223. function withdraw_nonce_account(address nonce_pubkey, address authorized_pubkey, address to_pubkey, uint64 lamports) internal view {
  224. AccountMeta[5] metas = [
  225. AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
  226. AccountMeta({pubkey: to_pubkey, is_signer: false, is_writable: true}),
  227. AccountMeta({pubkey: recentBlockHashes, is_signer: false, is_writable: false}),
  228. AccountMeta({pubkey: rentAddress, is_signer: false, is_writable: false}),
  229. AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
  230. ];
  231. bytes bincode = abi.encode(uint32(Instruction.WithdrawNonceAccount), lamports);
  232. systemAddress.call{accounts: metas}(bincode);
  233. }
  234. /// Change the entity authorized to execute nonce instructions on the account
  235. ///
  236. /// @param nonce_pubkey the public key for the nonce account
  237. /// @param authorized_pubkey the public key for the entity authorized to execute instructins on the account
  238. /// @param new_authority
  239. function authorize_nonce_account(address nonce_pubkey, address authorized_pubkey, address new_authority) internal view {
  240. AccountMeta[2] metas = [
  241. AccountMeta({pubkey: nonce_pubkey, is_signer: false, is_writable: true}),
  242. AccountMeta({pubkey: authorized_pubkey, is_signer: true, is_writable: false})
  243. ];
  244. bytes bincode = abi.encode(uint32(Instruction.AuthorizeNonceAccount), new_authority);
  245. systemAddress.call{accounts: metas}(bincode);
  246. }
  247. /// One-time idempotent upgrade of legacy nonce version in order to bump them out of chain domain.
  248. ///
  249. /// @param nonce the public key for the nonce account
  250. // This is not available on Solana v1.9.15
  251. function upgrade_nonce_account(address nonce) internal view {
  252. AccountMeta[1] meta = [
  253. AccountMeta({pubkey: nonce, is_signer: false, is_writable: true})
  254. ];
  255. bytes bincode = abi.encode(uint32(Instruction.UpgradeNonceAccount));
  256. systemAddress.call{accounts: meta}(bincode);
  257. }
  258. }