Para probar una función internal en Solidity, crea un contrato hijo que herede del contrato que se está probando, envuelve la función internal del contrato padre con una external, y luego prueba la función external en el hijo.
Foundry llama a este contrato que hereda un “harness”, aunque otros lo llaman un “fixture”.
No cambies la función para que se vuelva virtual o public para facilitar su extensión; lo que quieres es probar el contrato que realmente vas a desplegar.
Aquí tienes un ejemplo.
contract InternalFunction {
function calculateReward(uint256 depositTime) **internal** view returns (uint256 reward) {
reward = (block.timestamp - depositTime) \* REWARD\_RATE\_PER\_SECOND;
}
}
La función anterior otorga una tasa de recompensa lineal por cada unidad de tiempo que transcurre.
El fixture (o harness) se vería así:
contract InternalFunctionHarness is InternalFunction {
function calculateReward(uint256 depositTime) **external** view returns (uint256 reward) {
reward = super.calculateReward(depositTime);
}
}
Cuando llamas a una función padre que tiene el mismo nombre que la del hijo, debes usar la palabra clave super, o de lo contrario la función se llamará a sí misma y entrará en una recursión infinita.
Alternativamente, puedes etiquetar explícitamente tu función de prueba como un harness o fixture de la siguiente manera:
contract InternalFunctionHarness is InternalFunction {
function calculateReward\_HARNESS(uint256 depositTime) **external** view returns (uint256 reward) {
reward = calculateReward(depositTime);
}
}
No cambies la función para que sea public
Cambiar la función para que se vuelva public no es una buena solución porque esto aumentará el tamaño del contrato. Si una función no necesita ser public, entonces no la hagas public. Esto aumentará el costo de gas tanto para el despliegue como para la ejecución de las otras funciones.
Cuando un contrato recibe una transacción, debe comparar el selector de función con todos los selectores public en una búsqueda lineal o binaria. En cualquier caso, tiene más selectores entre los que buscar. Además, el selector añadido es bytecode adicional que incrementa el costo de despliegue.
No sobrescribas funciones virtual
Supongamos que tuviéramos el siguiente contrato:
contract InternalFunction {
function calculateReward(uint256 depositTime) **internal** view virtual returns (uint256 reward) {
reward = (block.timestamp - depositTime) \* REWARD\_RATE\_PER\_SECOND;
}
}
Podría ser tentador simplemente sobrescribirla en el fixture por conveniencia, pero esto no es recomendable ya que terminas duplicando código y si tu implementación en el harness diverge del contrato padre, ya no estarás probando realmente tu lógica de negocio.
Ten en cuenta que este método nos obliga a copiar y pegar el código original:
contract InternalFunctionHarness is InternalFunction {
function calculateReward(uint256 depositTime) **external** view override returns (uint256 reward) {
reward = (block.timestamp - depositTime) \* REWARD\_RATE\_PER\_SECOND;
}
}
¿Qué pasa con las pruebas de funciones private en Solidity?
No hay manera de probar funciones private en Solidity ya que no son visibles para el contrato hijo. La distinción entre una función internal y una función private no existe después de que el contrato es compilado. Por lo tanto, puedes cambiar las funciones private para que sean internal sin ningún efecto negativo en el costo de gas.
Como ejercicio para el lector, realiza un benchmark del siguiente código para comprobar que cambiar foo para que sea internal no afecta el costo de gas.
contract A {
// change this to be private
function foo() **internal** pure returns (uint256 f) {
f = 2;
}
function bar() **internal** pure returns (uint256 b) {
b = foo();
}
}
contract B is A {
// 146 gas: 0.8.7 no optimizer
function baz() **external** pure returns (uint256 b) {
b = bar();
}
}
Aprende más
Consulta nuestro Solidity Bootcamp avanzado para aprender más metodologías de prueba avanzadas.
También tenemos un tutorial de Solidity gratuito para ayudarte a empezar.
Publicado originalmente el 6 de abril de 2023